diff options
Diffstat (limited to 'src/lib')
63 files changed, 7194 insertions, 5028 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 95e431e5..4e6a901a 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -10,34 +10,46 @@ lib_LTLIBRARIES = \ libtalermerchant.la libtalermerchant_la_LDFLAGS = \ - -version-info 3:0:0 \ + -version-info 6:0:0 \ -no-undefined libtalermerchant_la_SOURCES = \ merchant_api_curl_defaults.c merchant_api_curl_defaults.h \ - merchant_api_common.c \ + merchant_api_common.c merchant_api_common.h \ + merchant_api_delete_account.c \ merchant_api_delete_instance.c \ merchant_api_delete_order.c \ + merchant_api_delete_otp_device.c \ merchant_api_delete_product.c \ - merchant_api_delete_reserve.c \ + merchant_api_delete_template.c \ merchant_api_delete_transfer.c \ + merchant_api_delete_webhook.c \ + merchant_api_get_account.c \ + merchant_api_get_accounts.c \ merchant_api_get_config.c \ merchant_api_get_instance.c \ merchant_api_get_instances.c \ merchant_api_get_kyc.c \ merchant_api_get_orders.c \ + merchant_api_get_otp_device.c \ + merchant_api_get_otp_devices.c \ merchant_api_get_product.c \ merchant_api_get_products.c \ - merchant_api_get_reserve.c \ - merchant_api_get_reserves.c \ - merchant_api_get_tips.c \ merchant_api_get_transfers.c \ + merchant_api_get_template.c \ + merchant_api_get_templates.c \ + merchant_api_get_webhook.c \ + merchant_api_get_webhooks.c \ merchant_api_lock_product.c \ merchant_api_merchant_get_order.c \ - merchant_api_merchant_get_tip.c \ + merchant_api_patch_account.c \ merchant_api_patch_instance.c \ merchant_api_patch_order_forget.c \ + merchant_api_patch_otp_device.c \ merchant_api_patch_product.c \ + merchant_api_patch_template.c \ + merchant_api_patch_webhook.c \ + merchant_api_post_account.c \ merchant_api_post_instance_auth.c \ merchant_api_post_instances.c \ merchant_api_post_orders.c \ @@ -46,35 +58,30 @@ libtalermerchant_la_SOURCES = \ merchant_api_post_order_paid.c \ merchant_api_post_order_pay.c \ merchant_api_post_order_refund.c \ + merchant_api_post_otp_devices.c \ merchant_api_post_products.c \ - merchant_api_post_reserves.c \ merchant_api_post_transfers.c \ - merchant_api_tip_authorize.c \ - merchant_api_tip_pickup.c \ - merchant_api_tip_pickup2.c \ - merchant_api_wallet_get_tip.c \ + merchant_api_post_templates.c \ + merchant_api_post_tokenfamilies.c \ + merchant_api_post_using_templates.c \ + merchant_api_post_webhooks.c \ merchant_api_wallet_get_order.c \ + merchant_api_wallet_get_template.c \ merchant_api_wallet_post_order_refund.c libtalermerchant_la_LIBADD = \ -ltalerexchange \ -ltalercurl \ -ltalerjson \ + -ltalerkyclogic \ -ltalerutil \ -lgnunetcurl \ -lgnunetjson \ -lgnunetutil \ -ljansson \ + -lcurl \ $(XLIB) -if HAVE_LIBCURL -libtalermerchant_la_LIBADD += -lcurl -else -if HAVE_LIBGNURL -libtalermerchant_la_LIBADD += -lgnurl -endif -endif - check_PROGRAMS = \ test_merchant_api_common diff --git a/src/lib/merchant_api_common.c b/src/lib/merchant_api_common.c index 7e22f645..f5569ce3 100644 --- a/src/lib/merchant_api_common.c +++ b/src/lib/merchant_api_common.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -18,10 +18,12 @@ * @file merchant_api_common.c * @brief Implementation of common logic for libtalermerchant * @author Christian Grothoff + * @author Priscilla Huang */ #include "platform.h" #include <curl/curl.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include <gnunet/gnunet_uri_lib.h> #include <taler/taler_json_lib.h> @@ -105,31 +107,7 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response, } -char * -TALER_MERCHANT_baseurl_add_instance (const char *base_url, - const char *instance_id) -{ - char *ret; - bool end_sl; - - if ('\0' == *base_url) - { - GNUNET_break (0); - return NULL; - } - end_sl = '/' == base_url[strlen (base_url) - 1]; - - GNUNET_asprintf (&ret, - (end_sl) - ? "%sinstances/%s/" - : "%s/instances/%s/", - base_url, - instance_id); - return ret; -} - - -int +enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_pay_uri (const char *pay_uri, struct TALER_MERCHANT_PayUriData *parse_data) { @@ -267,7 +245,7 @@ TALER_MERCHANT_parse_pay_uri_free ( } -int +enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_refund_uri ( const char *refund_uri, struct TALER_MERCHANT_RefundUriData *parse_data) @@ -371,3 +349,138 @@ TALER_MERCHANT_parse_refund_uri_free ( GNUNET_free (parse_data->order_id); GNUNET_free (parse_data->ssid); } + + +void +TALER_MERCHANT_handle_order_creation_response_ ( + TALER_MERCHANT_PostOrdersCallback cb, + void *cb_cls, + long response_code, + const json_t *json) +{ + struct TALER_MERCHANT_PostOrdersReply por = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + struct TALER_ClaimTokenP token; + + switch (response_code) + { + case 0: + por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + bool no_token; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("order_id", + &por.details.ok.order_id), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("token", + &token), + &no_token), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + por.hr.http_status = 0; + por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + else + { + if (! no_token) + por.details.ok.token = &token; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + json_dumpf (json, + stderr, + JSON_INDENT (2)); + por.hr.ec = TALER_JSON_get_error_code (json); + por.hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us or + the merchant is buggy (or API version conflict); + just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + por.hr.ec = TALER_JSON_get_error_code (json); + por.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, merchant says one + of the signatures is invalid; as we checked them, + this should never happen, we should pass the JSON + reply to the application */ + por.hr.ec = TALER_JSON_get_error_code (json); + por.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + por.hr.ec = TALER_JSON_get_error_code (json); + por.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + por.hr.ec = TALER_JSON_get_error_code (json); + por.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_GONE: + /* The quantity of some product requested was not available. */ + { + + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ( + "product_id", + &por.details.gone.product_id), + GNUNET_JSON_spec_uint64 ( + "requested_quantity", + &por.details.gone.requested_quantity), + GNUNET_JSON_spec_uint64 ( + "available_quantity", + &por.details.gone.available_quantity), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ( + "restock_expected", + &por.details.gone.restock_expected), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + por.hr.http_status = 0; + por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + } + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + por.hr.ec = TALER_JSON_get_error_code (json); + por.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + por.hr.ec = TALER_JSON_get_error_code (json); + por.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) por.hr.ec); + GNUNET_break_op (0); + break; + } // end of switch + cb (cb_cls, + &por); +} diff --git a/src/lib/merchant_api_common.h b/src/lib/merchant_api_common.h new file mode 100644 index 00000000..19a92149 --- /dev/null +++ b/src/lib/merchant_api_common.h @@ -0,0 +1,61 @@ +/* + This file is part of TALER + Copyright (C) 2020-2023 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_common.h + * @brief Implementation of common logic for libtalermerchant + * @author Christian Grothoff + * @author Priscilla Huang + */ +#ifndef MERCHANT_API_COMMON_H +#define MERCHANT_API_COMMON_H +#include "taler_merchant_service.h" + + +/** + * Function called when we're done processing a + * HTTP POST request to create an order. + * + * @param cb callback + * @param cb_cls closure for @a cb + * @param response_code HTTP response code, 0 on error + * @param json response body, NULL if not JSON + */ +void +TALER_MERCHANT_handle_order_creation_response_ ( + TALER_MERCHANT_PostOrdersCallback cb, + void *cb_cls, + long response_code, + const json_t *json); + + +/** + * Take a @a response from the merchant API that (presumably) contains + * error details and setup the corresponding @a hr structure. Internally + * used to convert merchant's responses in to @a hr. + * + * @param response if NULL we will report #TALER_EC_GENERIC_INVALID_RESPONSE in `ec` + * @param http_status http status to use + * @param[out] hr response object to initialize, fields will + * only be valid as long as @a response is valid as well + */ +void +TALER_MERCHANT_parse_error_details_ (const json_t *response, + unsigned int http_status, + struct TALER_MERCHANT_HttpResponse *hr); + + +#endif diff --git a/src/lib/merchant_api_curl_defaults.c b/src/lib/merchant_api_curl_defaults.c index 34e4aad8..f3c4ee18 100644 --- a/src/lib/merchant_api_curl_defaults.c +++ b/src/lib/merchant_api_curl_defaults.c @@ -19,7 +19,8 @@ * @brief curl easy handle defaults * @author Florian Dold */ - +#include "platform.h" +#include <taler/taler_curl_lib.h> #include "merchant_api_curl_defaults.h" @@ -38,22 +39,14 @@ TALER_MERCHANT_curl_easy_get_ (const char *url) curl_easy_setopt (eh, CURLOPT_URL, url)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_FOLLOWLOCATION, - 1L)); + TALER_curl_set_secure_redirect_policy (eh, + url); /* Enable compression (using whatever curl likes), see https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */ GNUNET_break (CURLE_OK == curl_easy_setopt (eh, CURLOPT_ACCEPT_ENCODING, "")); - /* limit MAXREDIRS to 5 as a simple security measure against - a potential infinite loop caused by a malicious target */ - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_MAXREDIRS, - 5L)); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_TCP_FASTOPEN, diff --git a/src/lib/merchant_api_delete_account.c b/src/lib/merchant_api_delete_account.c new file mode 100644 index 00000000..42d8bc5d --- /dev/null +++ b/src/lib/merchant_api_delete_account.c @@ -0,0 +1,185 @@ +/* + 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 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_delete_account.c + * @brief Implementation of the DELETE /private/account/$H_WIRE request of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a DELETE /accounts/$ID operation. + */ +struct TALER_MERCHANT_AccountDeleteHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_AccountDeleteCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP DELETE /accounts/$H_WIRE request. + * + * @param cls the `struct TALER_MERCHANT_AccountDeleteHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_delete_account_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_AccountDeleteHandle *adh = cls; + const json_t *json = response; + struct TALER_MERCHANT_AccountDeleteResponse adr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + adh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /accounts/$H_WIRE response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_UNAUTHORIZED: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + break; + default: + /* unexpected response code */ + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for DELETE /account/ID\n", + (unsigned int) response_code, + (int) adr.hr.ec); + break; + } + adh->cb (adh->cb_cls, + &adr); + TALER_MERCHANT_account_delete_cancel (adh); +} + + +struct TALER_MERCHANT_AccountDeleteHandle * +TALER_MERCHANT_account_delete ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct TALER_MerchantWireHashP *h_wire, + TALER_MERCHANT_AccountDeleteCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_AccountDeleteHandle *adh; + + adh = GNUNET_new (struct TALER_MERCHANT_AccountDeleteHandle); + adh->ctx = ctx; + adh->cb = cb; + adh->cb_cls = cb_cls; + { + char h_wire_str[sizeof (*h_wire) * 2]; + char *path; + char *end; + + end = GNUNET_STRINGS_data_to_string (h_wire, + sizeof (*h_wire), + h_wire_str, + sizeof (h_wire_str)); + *end = '\0'; + GNUNET_asprintf (&path, + "private/account/%s", + h_wire_str); + adh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == adh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (adh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + adh->url); + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (adh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_DELETE)); + adh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_delete_account_finished, + adh); + } + return adh; +} + + +void +TALER_MERCHANT_account_delete_cancel ( + struct TALER_MERCHANT_AccountDeleteHandle *adh) +{ + if (NULL != adh->job) + GNUNET_CURL_job_cancel (adh->job); + GNUNET_free (adh->url); + GNUNET_free (adh); +} diff --git a/src/lib/merchant_api_delete_order.c b/src/lib/merchant_api_delete_order.c index 33a12294..a0cf941f 100644 --- a/src/lib/merchant_api_delete_order.c +++ b/src/lib/merchant_api_delete_order.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2022 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 @@ -116,6 +116,7 @@ TALER_MERCHANT_order_delete ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const char *order_id, + bool force, TALER_MERCHANT_OrderDeleteCallback cb, void *cb_cls) { @@ -129,8 +130,11 @@ TALER_MERCHANT_order_delete ( char *path; GNUNET_asprintf (&path, - "private/orders/%s", - order_id); + "private/orders/%s%s", + order_id, + force + ? "?force=yes" + : ""); odh->url = TALER_url_join (backend_url, path, diff --git a/src/lib/merchant_api_delete_otp_device.c b/src/lib/merchant_api_delete_otp_device.c new file mode 100644 index 00000000..5397606c --- /dev/null +++ b/src/lib/merchant_api_delete_otp_device.c @@ -0,0 +1,184 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_delete_otp_device.c + * @brief Implementation of the DELETE /otp-devices/$ID request of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a DELETE /otp-devices/$ID operation. + */ +struct TALER_MERCHANT_OtpDeviceDeleteHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_OtpDeviceDeleteCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /otp-devices/$ID request. + * + * @param cls the `struct TALER_MERCHANT_OtpDeviceDeleteHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_delete_otp_device_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tdh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got DELETE /otp-devices/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + hr.ec = TALER_JSON_get_error_code (json); + 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) hr.ec); + break; + } + tdh->cb (tdh->cb_cls, + &hr); + TALER_MERCHANT_otp_device_delete_cancel (tdh); +} + + +struct TALER_MERCHANT_OtpDeviceDeleteHandle * +TALER_MERCHANT_otp_device_delete ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *otp_device_id, + TALER_MERCHANT_OtpDeviceDeleteCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh; + + tdh = GNUNET_new (struct TALER_MERCHANT_OtpDeviceDeleteHandle); + tdh->ctx = ctx; + tdh->cb = cb; + tdh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/otp-devices/%s", + otp_device_id); + tdh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tdh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tdh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tdh->url); + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (tdh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_DELETE)); + tdh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_delete_otp_device_finished, + tdh); + } + return tdh; +} + + +void +TALER_MERCHANT_otp_device_delete_cancel ( + struct TALER_MERCHANT_OtpDeviceDeleteHandle *tdh) +{ + if (NULL != tdh->job) + GNUNET_CURL_job_cancel (tdh->job); + GNUNET_free (tdh->url); + GNUNET_free (tdh); +} diff --git a/src/lib/merchant_api_delete_reserve.c b/src/lib/merchant_api_delete_reserve.c deleted file mode 100644 index 8062d040..00000000 --- a/src/lib/merchant_api_delete_reserve.c +++ /dev/null @@ -1,239 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 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 - <http://www.gnu.org/licenses/> -*/ -/** - * @file merchant_api_delete_reserve.c - * @brief Implementation of the DELETE /reserves/$RESERVE_PUB request of the merchant's HTTP API - * @author Jonathan Buchanan - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> - - -/** - * Handle for a DELETE /reserves/$RESERVE_PUB operation. - */ -struct TALER_MERCHANT_ReserveDeleteHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_ReserveDeleteCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - -}; - - -/** - * Function called when we're done processing the - * HTTP DELETE /reserves/$RESERVE_PUB request. - * - * @param cls the `struct TALER_MERCHANT_InstanceDeleteHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_delete_reserve_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_ReserveDeleteHandle *rdh = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - rdh->job = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Got /reserves/$ID response with status code %u\n", - (unsigned int) response_code); - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - 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) hr.ec); - break; - } - rdh->cb (rdh->cb_cls, - &hr); - TALER_MERCHANT_reserve_delete_cancel (rdh); -} - - -/** - * Delete the private key of a reserve. - * - * @param ctx the context - * @param backend_url HTTP base URL for the backend - * @param reserve_pub which reserve should be deleted - * @param purge purge instead of just deleting - * @param cb function to call with the backend's return - * @param cb_cls closure for @a config_cb - * @return the instances handle; NULL upon error - */ -static struct TALER_MERCHANT_ReserveDeleteHandle * -reserve_delete (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_ReservePublicKeyP *reserve_pub, - bool purge, - TALER_MERCHANT_ReserveDeleteCallback cb, - void *cb_cls) -{ - struct TALER_MERCHANT_ReserveDeleteHandle *rdh; - - rdh = GNUNET_new (struct TALER_MERCHANT_ReserveDeleteHandle); - rdh->ctx = ctx; - rdh->cb = cb; - rdh->cb_cls = cb_cls; - { - char res_str[sizeof (*reserve_pub) * 2]; - char arg_str[sizeof (res_str) + 32]; - char *end; - - end = GNUNET_STRINGS_data_to_string (reserve_pub, - sizeof (*reserve_pub), - res_str, - sizeof (res_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "private/reserves/%s", - res_str); - if (purge) - rdh->url = TALER_url_join (backend_url, - arg_str, - "purge", - "yes", - NULL); - else - rdh->url = TALER_url_join (backend_url, - arg_str, - NULL); - } - if (NULL == rdh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (rdh); - return NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - rdh->url); - { - CURL *eh; - - eh = TALER_MERCHANT_curl_easy_get_ (rdh->url); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_CUSTOMREQUEST, - MHD_HTTP_METHOD_DELETE)); - rdh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_delete_reserve_finished, - rdh); - } - return rdh; -} - - -struct TALER_MERCHANT_ReserveDeleteHandle * -TALER_MERCHANT_reserve_delete ( - struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_ReservePublicKeyP *reserve_pub, - TALER_MERCHANT_ReserveDeleteCallback cb, - void *cb_cls) -{ - return reserve_delete (ctx, - backend_url, - reserve_pub, - false, - cb, - cb_cls); -} - - -struct TALER_MERCHANT_ReserveDeleteHandle * -TALER_MERCHANT_reserve_purge (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_ReservePublicKeyP *reserve_pub, - TALER_MERCHANT_ReserveDeleteCallback cb, - void *cb_cls) -{ - return reserve_delete (ctx, - backend_url, - reserve_pub, - true, - cb, - cb_cls); -} - - -void -TALER_MERCHANT_reserve_delete_cancel ( - struct TALER_MERCHANT_ReserveDeleteHandle *rdh) -{ - if (NULL != rdh->job) - GNUNET_CURL_job_cancel (rdh->job); - GNUNET_free (rdh->url); - GNUNET_free (rdh); -} diff --git a/src/lib/merchant_api_delete_template.c b/src/lib/merchant_api_delete_template.c new file mode 100644 index 00000000..b2083cc9 --- /dev/null +++ b/src/lib/merchant_api_delete_template.c @@ -0,0 +1,184 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_delete_template.c + * @brief Implementation of the DELETE /templates/$ID request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a DELETE /templates/$ID operation. + */ +struct TALER_MERCHANT_TemplateDeleteHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TemplateDeleteCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /templates/$ID request. + * + * @param cls the `struct TALER_MERCHANT_TemplateDeleteHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_delete_template_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TemplateDeleteHandle *tdh = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tdh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /templates/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + hr.ec = TALER_JSON_get_error_code (json); + 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) hr.ec); + break; + } + tdh->cb (tdh->cb_cls, + &hr); + TALER_MERCHANT_template_delete_cancel (tdh); +} + + +struct TALER_MERCHANT_TemplateDeleteHandle * +TALER_MERCHANT_template_delete ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *template_id, + TALER_MERCHANT_TemplateDeleteCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TemplateDeleteHandle *tdh; + + tdh = GNUNET_new (struct TALER_MERCHANT_TemplateDeleteHandle); + tdh->ctx = ctx; + tdh->cb = cb; + tdh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/templates/%s", + template_id); + tdh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tdh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tdh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tdh->url); + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (tdh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_DELETE)); + tdh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_delete_template_finished, + tdh); + } + return tdh; +} + + +void +TALER_MERCHANT_template_delete_cancel ( + struct TALER_MERCHANT_TemplateDeleteHandle *tdh) +{ + if (NULL != tdh->job) + GNUNET_CURL_job_cancel (tdh->job); + GNUNET_free (tdh->url); + GNUNET_free (tdh); +} diff --git a/src/lib/merchant_api_delete_webhook.c b/src/lib/merchant_api_delete_webhook.c new file mode 100644 index 00000000..14ecdcd1 --- /dev/null +++ b/src/lib/merchant_api_delete_webhook.c @@ -0,0 +1,184 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_delete_template.c + * @brief Implementation of the DELETE /webhooks/$ID request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a DELETE /webhooks/$ID operation. + */ +struct TALER_MERCHANT_WebhookDeleteHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_WebhookDeleteCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /webhooks/$ID request. + * + * @param cls the `struct TALER_MERCHANT_WebhookDeleteHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_delete_webhook_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_WebhookDeleteHandle *wdh = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + wdh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /webhooks/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + hr.ec = TALER_JSON_get_error_code (json); + 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) hr.ec); + break; + } + wdh->cb (wdh->cb_cls, + &hr); + TALER_MERCHANT_webhook_delete_cancel (wdh); +} + + +struct TALER_MERCHANT_WebhookDeleteHandle * +TALER_MERCHANT_webhook_delete ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *webhook_id, + TALER_MERCHANT_WebhookDeleteCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_WebhookDeleteHandle *wdh; + + wdh = GNUNET_new (struct TALER_MERCHANT_WebhookDeleteHandle); + wdh->ctx = ctx; + wdh->cb = cb; + wdh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/webhooks/%s", + webhook_id); + wdh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wdh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wdh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wdh->url); + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (wdh->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_DELETE)); + wdh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_delete_webhook_finished, + wdh); + } + return wdh; +} + + +void +TALER_MERCHANT_webhook_delete_cancel ( + struct TALER_MERCHANT_WebhookDeleteHandle *wdh) +{ + if (NULL != wdh->job) + GNUNET_CURL_job_cancel (wdh->job); + GNUNET_free (wdh->url); + GNUNET_free (wdh); +} diff --git a/src/lib/merchant_api_get_account.c b/src/lib/merchant_api_get_account.c new file mode 100644 index 00000000..84595e80 --- /dev/null +++ b/src/lib/merchant_api_get_account.c @@ -0,0 +1,211 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_account.c + * @brief Implementation of the GET /accounts/$ID request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a GET /accounts/$ID operation. + */ +struct TALER_MERCHANT_AccountGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_AccountGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /accounts/$ID request. + * + * @param cls the `struct TALER_MERCHANT_AccountGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_account_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_AccountGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_AccountGetResponse tgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /accounts/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("salt", + &tgr.details.ok.ad.salt), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_web_url ("credit_facade_url", + &tgr.details.ok.ad.credit_facade_url), + NULL), + TALER_JSON_spec_payto_uri ("payto_uri", + &tgr.details.ok.ad.payto_uri), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &tgr.details.ok.ad.h_wire), + GNUNET_JSON_spec_bool ("active", + &tgr.details.ok.ad.active), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK == + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_account_get_cancel (tgh); + return; + } + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.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) tgr.hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_account_get_cancel (tgh); +} + + +struct TALER_MERCHANT_AccountGetHandle * +TALER_MERCHANT_account_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *instance_id, + const struct TALER_MerchantWireHashP *h_wire, + TALER_MERCHANT_AccountGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_AccountGetHandle *tgh; + CURL *eh; + + tgh = GNUNET_new (struct TALER_MERCHANT_AccountGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + { + char w_str[sizeof (*h_wire) * 2]; + char *path; + char *end; + + end = GNUNET_STRINGS_data_to_string (h_wire, + sizeof (*h_wire), + w_str, + sizeof (w_str)); + *end = '\0'; + GNUNET_asprintf (&path, + "private/accounts/%s", + w_str); + tgh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_account_finished, + tgh); + return tgh; +} + + +void +TALER_MERCHANT_account_get_cancel ( + struct TALER_MERCHANT_AccountGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} diff --git a/src/lib/merchant_api_get_accounts.c b/src/lib/merchant_api_get_accounts.c new file mode 100644 index 00000000..c08cd92d --- /dev/null +++ b/src/lib/merchant_api_get_accounts.c @@ -0,0 +1,247 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_accounts.c + * @brief Implementation of the GET /accounts request of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + +/** + * Maximum number of accounts permitted. + */ +#define MAX_ACCOUNTS 1024 + +/** + * Handle for a GET /accounts operation. + */ +struct TALER_MERCHANT_AccountsGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_AccountsGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Parse account information from @a ia. + * + * @param ia JSON array (or NULL!) with account data + * @param[in] tgr partially filled response + * @param tgh operation handle + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_accounts (const json_t *ia, + struct TALER_MERCHANT_AccountsGetResponse *tgr, + struct TALER_MERCHANT_AccountsGetHandle *tgh) +{ + unsigned int tmpl_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) tmpl_len) || + (tmpl_len > MAX_ACCOUNTS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_AccountEntry tmpl[GNUNET_NZL (tmpl_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_AccountEntry *ie = &tmpl[index]; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_payto_uri ("payto_uri", + &ie->payto_uri), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &ie->h_wire), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + tgr->details.ok.accounts_length = tmpl_len; + tgr->details.ok.accounts = tmpl; + tgh->cb (tgh->cb_cls, + tgr); + tgh->cb = NULL; /* just to be sure */ + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /accounts request. + * + * @param cls the `struct TALER_MERCHANT_AccountsGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_accounts_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_AccountsGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_AccountsGetResponse tgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /accounts response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + const json_t *accounts; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("accounts", + &accounts), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + if (GNUNET_OK == + parse_accounts (accounts, + &tgr, + tgh)) + { + TALER_MERCHANT_accounts_get_cancel (tgh); + return; + } + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + default: + /* unexpected response code */ + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.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) tgr.hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_accounts_get_cancel (tgh); +} + + +struct TALER_MERCHANT_AccountsGetHandle * +TALER_MERCHANT_accounts_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + TALER_MERCHANT_AccountsGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_AccountsGetHandle *tgh; + CURL *eh; + + tgh = GNUNET_new (struct TALER_MERCHANT_AccountsGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + tgh->url = TALER_url_join (backend_url, + "private/accounts", + NULL); + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_accounts_finished, + tgh); + return tgh; +} + + +void +TALER_MERCHANT_accounts_get_cancel ( + struct TALER_MERCHANT_AccountsGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} diff --git a/src/lib/merchant_api_get_config.c b/src/lib/merchant_api_get_config.c index 088434c5..9b501342 100644 --- a/src/lib/merchant_api_get_config.c +++ b/src/lib/merchant_api_get_config.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018, 2020 Taler Systems SA + Copyright (C) 2014-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 @@ -34,13 +34,22 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define MERCHANT_PROTOCOL_CURRENT 3 +#define MERCHANT_PROTOCOL_CURRENT 16 /** - * How many configs are we backwards compatible with? + * How many configs are we backwards-compatible with? */ -#define MERCHANT_PROTOCOL_AGE 1 +#define MERCHANT_PROTOCOL_AGE 4 +/** + * How many exchanges do we allow at most per merchant? + */ +#define MAX_EXCHANGES 1024 + +/** + * How many currency specs do we allow at most per merchant? + */ +#define MAX_CURRENCIES 1024 /** * @brief A handle for /config operations @@ -90,9 +99,9 @@ handle_config_finished (void *cls, { struct TALER_MERCHANT_ConfigGetHandle *vgh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_ConfigResponse cr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -104,78 +113,162 @@ handle_config_finished (void *cls, { case MHD_HTTP_OK: { - struct TALER_MERCHANT_ConfigInformation vi; - enum TALER_MERCHANT_VersionCompatibility vc = - TALER_MERCHANT_VC_PROTOCOL_ERROR; + const json_t *jcs; + const json_t *exchanges = NULL; + struct TALER_MERCHANT_ExchangeConfigInfo *eci = NULL; + unsigned int num_eci = 0; + unsigned int nspec; + struct TALER_JSON_ProtocolVersion pv; struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_object_const ("currencies", + &jcs), + /* Optional for v5 compatibility, remove + once we reduce age! */ + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("exchanges", + &exchanges), + NULL), GNUNET_JSON_spec_string ("currency", - &vi.currency), + &cr.details.ok.ci.currency), + TALER_JSON_spec_version ("version", + &pv), GNUNET_JSON_spec_string ("version", - &vi.version), + &cr.details.ok.ci.version), GNUNET_JSON_spec_end () }; + cr.details.ok.compat = TALER_MERCHANT_VC_PROTOCOL_ERROR; if (GNUNET_OK != GNUNET_JSON_parse (json, spec, NULL, NULL)) { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_break_op (0); + cr.hr.http_status = 0; + cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } - else + cr.details.ok.compat = TALER_MERCHANT_VC_MATCH; + if (MERCHANT_PROTOCOL_CURRENT < pv.current) + { + cr.details.ok.compat |= TALER_MERCHANT_VC_NEWER; + if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age) + cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE; + } + if (MERCHANT_PROTOCOL_CURRENT > pv.current) + { + cr.details.ok.compat |= TALER_MERCHANT_VC_OLDER; + if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current) + cr.details.ok.compat |= TALER_MERCHANT_VC_INCOMPATIBLE; + } + + nspec = (unsigned int) json_object_size (jcs); + if ( (nspec > MAX_CURRENCIES) || + (json_object_size (jcs) != (size_t) nspec) ) { - unsigned int age; - unsigned int revision; - unsigned int current; - - if (3 != sscanf (vi.version, - "%u:%u:%u", - ¤t, - &revision, - &age)) + GNUNET_break_op (0); + cr.hr.http_status = 0; + cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + if (NULL != exchanges) + { + num_eci = (unsigned int) json_object_size (exchanges); + if ( (num_eci > MAX_EXCHANGES) || + (json_object_size (exchanges) != (size_t) num_eci) ) { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_break_op (0); + cr.hr.http_status = 0; + cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } - else + eci = GNUNET_new_array (num_eci, + struct TALER_MERCHANT_ExchangeConfigInfo); + for (unsigned int i = 0; i<num_eci; i++) { - vc = TALER_MERCHANT_VC_MATCH; - if (MERCHANT_PROTOCOL_CURRENT < current) + struct TALER_MERCHANT_ExchangeConfigInfo *ei = &eci[i]; + const json_t *ej = json_array_get (exchanges, + i); + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("currency", + &ei->currency), + GNUNET_JSON_spec_string ("base_url", + &ei->base_url), + GNUNET_JSON_spec_fixed_auto ("master_pub", + &ei->master_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (ej, + ispec, + NULL, NULL)) { - vc |= TALER_MERCHANT_VC_NEWER; - if (MERCHANT_PROTOCOL_CURRENT < current - age) - vc |= TALER_MERCHANT_VC_INCOMPATIBLE; + GNUNET_break_op (0); + cr.hr.http_status = 0; + cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_free (eci); + break; } - if (MERCHANT_PROTOCOL_CURRENT > current) + } + } + { + struct TALER_CurrencySpecification *cspecs; + unsigned int off = 0; + json_t *obj; + const char *curr; + + cspecs = GNUNET_new_array (nspec, + struct TALER_CurrencySpecification); + cr.details.ok.num_cspecs = nspec; + cr.details.ok.cspecs = cspecs; + cr.details.ok.num_exchanges = (unsigned int) num_eci; + cr.details.ok.exchanges = eci; + json_object_foreach ((json_t *) jcs, curr, obj) + { + struct TALER_CurrencySpecification *cs = &cspecs[off++]; + struct GNUNET_JSON_Specification cspec[] = { + TALER_JSON_spec_currency_specification (curr, + curr, + cs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jcs, + cspec, + NULL, NULL)) { - vc |= TALER_MERCHANT_VC_OLDER; - if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > current) - vc |= TALER_MERCHANT_VC_INCOMPATIBLE; + GNUNET_break_op (0); + cr.hr.http_status = 0; + cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_free (eci); + TALER_CONFIG_free_currencies (off - 1, + cspecs); + break; } } + vgh->cb (vgh->cb_cls, + &cr); + GNUNET_free (eci); + TALER_CONFIG_free_currencies (nspec, + cspecs); } - vgh->cb (vgh->cb_cls, - &hr, - &vi, - vc); TALER_MERCHANT_config_get_cancel (vgh); return; } default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + cr.hr.ec = TALER_JSON_get_error_code (json); + cr.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) hr.ec); - vgh->cb (vgh->cb_cls, - &hr, - NULL, - TALER_MERCHANT_VC_PROTOCOL_ERROR); + (int) cr.hr.ec); break; } + vgh->cb (vgh->cb_cls, + &cr); TALER_MERCHANT_config_get_cancel (vgh); } diff --git a/src/lib/merchant_api_get_instance.c b/src/lib/merchant_api_get_instance.c index bb71a1ed..76f3691a 100644 --- a/src/lib/merchant_api_get_instance.c +++ b/src/lib/merchant_api_get_instance.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018, 2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -28,6 +28,7 @@ #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> +#include <taler/taler_kyclogic_lib.h> #include <taler/taler_signatures.h> @@ -79,9 +80,9 @@ handle_get_instance_finished (void *cls, { struct TALER_MERCHANT_InstanceGetHandle *igh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_InstanceGetResponse igr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; igh->job = NULL; @@ -92,135 +93,76 @@ handle_get_instance_finished (void *cls, { case MHD_HTTP_OK: { - json_t *accounts; - const char *name; - struct TALER_MerchantPublicKeyP merchant_pub; - json_t *address; - json_t *jurisdiction; - struct TALER_Amount default_max_wire_fee; - uint32_t default_wire_fee_amortization; - struct TALER_Amount default_max_deposit_fee; - struct GNUNET_TIME_Relative default_wire_transfer_delay; - struct GNUNET_TIME_Relative default_pay_delay; + const char *uts; + const json_t *address; + const json_t *jurisdiction; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("accounts", - &accounts), - GNUNET_JSON_spec_string ("name", - &name), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &merchant_pub), - GNUNET_JSON_spec_json ("address", - &address), - GNUNET_JSON_spec_json ("jurisdiction", - &jurisdiction), - TALER_JSON_spec_amount_any ("default_max_wire_fee", - &default_max_wire_fee), - GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization", - &default_wire_fee_amortization), - TALER_JSON_spec_amount_any ("default_max_deposit_fee", - &default_max_deposit_fee), - GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay", - &default_wire_transfer_delay), - GNUNET_JSON_spec_relative_time ("default_pay_delay", - &default_pay_delay), + GNUNET_JSON_spec_string ( + "name", + &igr.details.ok.details.name), + GNUNET_JSON_spec_string ( + "user_type", + &uts), + GNUNET_JSON_spec_fixed_auto ( + "merchant_pub", + &igr.details.ok.details.merchant_pub), + GNUNET_JSON_spec_object_const ( + "address", + &address), + GNUNET_JSON_spec_object_const ( + "jurisdiction", + &jurisdiction), + GNUNET_JSON_spec_bool ( + "use_stefan", + &igr.details.ok.details.use_stefan), + GNUNET_JSON_spec_relative_time ( + "default_wire_transfer_delay", + &igr.details.ok.details.default_wire_transfer_delay), + GNUNET_JSON_spec_relative_time ( + "default_pay_delay", + &igr.details.ok.details.default_pay_delay), GNUNET_JSON_spec_end () }; - if ( (GNUNET_OK == - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) && - (json_is_array (accounts)) ) + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) { - unsigned int accounts_length = json_array_size (accounts); - struct TALER_MERCHANT_Account aa[accounts_length]; - const char *payto_uris[accounts_length]; - size_t index; - json_t *value; - int ret = GNUNET_OK; - - memset (payto_uris, 0, sizeof (payto_uris)); - json_array_foreach (accounts, index, value) - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("salt", - &aa[index].salt), - GNUNET_JSON_spec_string ("payto_uri", - &payto_uris[index]), - GNUNET_JSON_spec_fixed_auto ("h_wire", - &aa[index].h_wire), - GNUNET_JSON_spec_bool ("active", - &aa[index].active), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - break; - } - aa[index].payto_uri = payto_uris[index]; - } - - if (GNUNET_OK == ret) - { - struct TALER_MERCHANT_InstanceDetails details = { - .name = name, - .merchant_pub = &merchant_pub, - .address = address, - .jurisdiction = jurisdiction, - .default_max_wire_fee = &default_max_wire_fee, - .default_wire_fee_amortization = default_wire_fee_amortization, - .default_max_deposit_fee = &default_max_deposit_fee, - .default_wire_transfer_delay = default_wire_transfer_delay, - .default_pay_delay = default_pay_delay - }; - - igh->cb (igh->cb_cls, - &hr, - accounts_length, - aa, - &details); - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_instance_get_cancel (igh); - return; - } + GNUNET_break_op (0); + igr.hr.http_status = 0; + igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - GNUNET_JSON_parse_free (spec); - break; + igr.details.ok.details.address = address; + igr.details.ok.details.jurisdiction = jurisdiction; + igh->cb (igh->cb_cls, + &igr); + TALER_MERCHANT_instance_get_cancel (igh); + return; } case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + igr.hr.ec = TALER_JSON_get_error_code (json); + igr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_NOT_FOUND: /* instance does not exist */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + igr.hr.ec = TALER_JSON_get_error_code (json); + igr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + 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) hr.ec); + (int) igr.hr.ec); break; } igh->cb (igh->cb_cls, - &hr, - 0, - NULL, - NULL); + &igr); TALER_MERCHANT_instance_get_cancel (igh); } diff --git a/src/lib/merchant_api_get_instances.c b/src/lib/merchant_api_get_instances.c index 52a462b9..c47c4895 100644 --- a/src/lib/merchant_api_get_instances.c +++ b/src/lib/merchant_api_get_instances.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018, 2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -32,6 +32,11 @@ /** + * Maximum number of instances permitted. + */ +#define MAX_INSTANCES 1024 + +/** * Handle for a GET /instances operation. */ struct TALER_MERCHANT_InstancesGetHandle @@ -67,79 +72,75 @@ struct TALER_MERCHANT_InstancesGetHandle /** * Parse instance information from @a ia. * + * @param json overall reply body * @param ia JSON array (or NULL!) with instance data * @param igh operation handle * @return #GNUNET_OK on success */ -static int -parse_instances (const json_t *ia, +static enum GNUNET_GenericReturnValue +parse_instances (const json_t *json, + const json_t *ia, struct TALER_MERCHANT_InstancesGetHandle *igh) { - unsigned int iis_len = json_array_size (ia); - struct TALER_MERCHANT_InstanceInformation iis[iis_len]; - size_t index; - json_t *value; - int ret; + unsigned int iis_len = (unsigned int) json_array_size (ia); - ret = GNUNET_OK; - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_InstanceInformation *ii = &iis[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("name", - &ii->name), - GNUNET_JSON_spec_string ("id", - &ii->id), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &ii->merchant_pub), - GNUNET_JSON_spec_json ("payment_targets", - &ii->payment_targets), - GNUNET_JSON_spec_end () + if ( (json_array_size (ia) != (size_t) iis_len) || + (iis_len > MAX_INSTANCES) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_InstanceInformation iis[GNUNET_NZL (iis_len)]; + size_t index; + json_t *value; + struct TALER_MERCHANT_InstancesGetResponse igr = { + .hr.http_status = MHD_HTTP_OK, + .hr.reply = json, + .details.ok.iis_length = iis_len, + .details.ok.iis = iis }; - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - continue; - } - if (! json_is_array (ii->payment_targets)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - break; - } - for (unsigned int i = 0; i<json_array_size (ii->payment_targets); i++) - { - if (! json_is_string (json_array_get (ii->payment_targets, - i))) + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_InstanceInformation *ii = &iis[index]; + const char *uts; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &ii->name), + GNUNET_JSON_spec_string ("user_type", + &uts), + GNUNET_JSON_spec_string ("id", + &ii->id), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &ii->merchant_pub), + GNUNET_JSON_spec_array_const ("payment_targets", + &ii->payment_targets), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) { GNUNET_break_op (0); - ret = GNUNET_SYSERR; - break; + return GNUNET_SYSERR; } - } - if (GNUNET_SYSERR == ret) - break; - } - if (GNUNET_OK == ret) - { - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = MHD_HTTP_OK - }; - + for (size_t i = 0; i<json_array_size (ii->payment_targets); i++) + { + if (! json_is_string (json_array_get (ii->payment_targets, + i))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + } /* for all instances */ igh->cb (igh->cb_cls, - &hr, - iis_len, - iis); + &igr); igh->cb = NULL; /* just to be sure */ } - for (unsigned int i = 0; i<iis_len; i++) - if (NULL != iis[i].payment_targets) - json_decref (iis[i].payment_targets); - return ret; + return GNUNET_OK; } @@ -158,9 +159,9 @@ handle_instances_finished (void *cls, { struct TALER_MERCHANT_InstancesGetHandle *igh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_InstancesGetResponse igr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; igh->job = NULL; @@ -171,10 +172,10 @@ handle_instances_finished (void *cls, { case MHD_HTTP_OK: { - json_t *instances; + const json_t *instances; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("instances", - &instances), + GNUNET_JSON_spec_array_const ("instances", + &instances), GNUNET_JSON_spec_end () }; @@ -183,47 +184,38 @@ handle_instances_finished (void *cls, spec, NULL, NULL)) { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + igr.hr.http_status = 0; + igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } - else + if (GNUNET_OK == + parse_instances (json, + instances, + igh)) { - if ( (! json_is_array (instances)) || - (GNUNET_OK == - parse_instances (instances, - igh)) ) - { - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_instances_get_cancel (igh); - return; - } - else - { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } + TALER_MERCHANT_instances_get_cancel (igh); + return; } - GNUNET_JSON_parse_free (spec); + igr.hr.http_status = 0; + igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + igr.hr.ec = TALER_JSON_get_error_code (json); + igr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + 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) hr.ec); + (int) igr.hr.ec); break; } igh->cb (igh->cb_cls, - &hr, - 0, - NULL); + &igr); TALER_MERCHANT_instances_get_cancel (igh); } diff --git a/src/lib/merchant_api_get_kyc.c b/src/lib/merchant_api_get_kyc.c index 2a1725cf..d2a819ea 100644 --- a/src/lib/merchant_api_get_kyc.c +++ b/src/lib/merchant_api_get_kyc.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021 Taler Systems SA + Copyright (C) 2023 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 @@ -32,6 +32,11 @@ /** + * Maximum length of the KYC arrays supported. + */ +#define MAX_KYC 1024 + +/** * Handle for a GET /kyc operation. */ struct TALER_MERCHANT_KycGetHandle @@ -76,70 +81,91 @@ struct TALER_MERCHANT_KycGetHandle static enum GNUNET_GenericReturnValue parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, struct TALER_MERCHANT_KycResponse *kr, - json_t *pends, - json_t *touts) + const json_t *pends, + const json_t *touts) { - unsigned int num_pends = json_array_size (pends); - unsigned int num_touts = json_array_size (touts); - struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[GNUNET_NZL ( - num_pends)]; - struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[GNUNET_NZL ( - num_touts)]; - - for (unsigned int i = 0; i<num_pends; i++) + unsigned int num_pends = (unsigned int) json_array_size (pends); + unsigned int num_touts = (unsigned int) json_array_size (touts); + + if ( (json_array_size (pends) != (size_t) num_pends) || + (num_pends > MAX_KYC) ) { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("kyc_url", - &pending_kycs[i].kyc_url), - GNUNET_JSON_spec_string ("exchange_url", - &pending_kycs[i].exchange_url), - GNUNET_JSON_spec_string ("payto_uri", - &pending_kycs[i].payto_uri), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (pends, - i), - spec, - NULL, NULL)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } + GNUNET_break (0); + return GNUNET_SYSERR; + } + if ( (json_array_size (touts) != (size_t) num_touts) || + (num_touts > MAX_KYC) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; } - for (unsigned int i = 0; i<num_touts; i++) + { - uint32_t hs; - uint32_t ec; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("exchange_url", - &timeout_kycs[i].exchange_url), - GNUNET_JSON_spec_uint32 ("exchange_code", - &ec), - GNUNET_JSON_spec_uint32 ("exchange_http_status", - &hs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (touts, - i), - spec, - NULL, NULL)) + struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[ + GNUNET_NZL (num_pends)]; + struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[ + GNUNET_NZL (num_touts)]; + + memset (pending_kycs, + 0, + sizeof (pending_kycs)); + for (unsigned int i = 0; i<num_pends; i++) { - GNUNET_break (0); - return GNUNET_SYSERR; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_web_url ("kyc_url", + &pending_kycs[i].kyc_url), + NULL), + TALER_JSON_spec_aml_decision ("aml_status", + &pending_kycs[i].aml_status), + TALER_JSON_spec_web_url ("exchange_url", + &pending_kycs[i].exchange_url), + TALER_JSON_spec_payto_uri ("payto_uri", + &pending_kycs[i].payto_uri), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (pends, + i), + spec, + NULL, NULL)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } } - timeout_kycs[i].exchange_http_status = (unsigned int) hs; - timeout_kycs[i].exchange_code = (enum TALER_ErrorCode) ec; + for (unsigned int i = 0; i<num_touts; i++) + { + uint32_t hs; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_web_url ("exchange_url", + &timeout_kycs[i].exchange_url), + TALER_JSON_spec_ec ("exchange_code", + &timeout_kycs[i].exchange_code), + GNUNET_JSON_spec_uint32 ("exchange_http_status", + &hs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (touts, + i), + spec, + NULL, NULL)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + timeout_kycs[i].exchange_http_status = (unsigned int) hs; + } + kr->details.kyc_status.pending_kycs = pending_kycs; + kr->details.kyc_status.timeout_kycs = timeout_kycs; + kr->details.kyc_status.pending_kycs_length = num_pends; + kr->details.kyc_status.timeout_kycs_length = num_touts; + kyc->cb (kyc->cb_cls, + kr); } - kr->details.kyc_status.pending_kycs = pending_kycs; - kr->details.kyc_status.timeout_kycs = timeout_kycs; - kr->details.kyc_status.pending_kycs_length = num_pends; - kr->details.kyc_status.timeout_kycs_length = num_touts; - kyc->cb (kyc->cb_cls, - kr); return GNUNET_OK; } @@ -176,13 +202,13 @@ handle_get_kyc_finished (void *cls, case MHD_HTTP_BAD_GATEWAY: case MHD_HTTP_GATEWAY_TIMEOUT: { - json_t *pends; - json_t *touts; + const json_t *pends; + const json_t *touts; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("pending_kycs", - &pends), - GNUNET_JSON_spec_json ("timeout_kycs", - &touts), + GNUNET_JSON_spec_array_const ("pending_kycs", + &pends), + GNUNET_JSON_spec_array_const ("timeout_kycs", + &touts), GNUNET_JSON_spec_end () }; @@ -195,20 +221,17 @@ handle_get_kyc_finished (void *cls, kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - if ( (! json_is_array (pends)) || - (! json_is_array (touts)) || - (GNUNET_OK != - parse_kyc (kyc, - &kr, - pends, - touts)) ) + if (GNUNET_OK != + parse_kyc (kyc, + &kr, + pends, + touts)) { kr.hr.http_status = 0; kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } /* parse_kyc called the continuation already */ - GNUNET_JSON_parse_free (spec); TALER_MERCHANT_kyc_get_cancel (kyc); return; } @@ -217,6 +240,8 @@ handle_get_kyc_finished (void *cls, kr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; + case MHD_HTTP_SERVICE_UNAVAILABLE: + break; default: /* unexpected response code */ kr.hr.ec = TALER_JSON_get_error_code (json); @@ -258,17 +283,19 @@ kyc_get (struct GNUNET_CURL_Context *ctx, struct TALER_MERCHANT_KycGetHandle *kyc; CURL *eh; char timeout_ms[32]; + unsigned int tms; kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle); kyc->ctx = ctx; kyc->cb = cb; kyc->cb_cls = cb_cls; + tms = (unsigned int) (timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS. + rel_value_us); GNUNET_snprintf (timeout_ms, sizeof (timeout_ms), - "%llu", - (unsigned long long) (timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS. - rel_value_us)); + "%u", + tms); kyc->url = TALER_url_join (url, "kyc", "h_wire", @@ -296,6 +323,13 @@ kyc_get (struct GNUNET_CURL_Context *ctx, "Requesting URL '%s'\n", kyc->url); eh = TALER_MERCHANT_curl_easy_get_ (kyc->url); + if (0 != tms) + { + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); + } kyc->job = GNUNET_CURL_job_add (ctx, eh, &handle_get_kyc_finished, @@ -319,7 +353,7 @@ TALER_MERCHANT_kyc_get (struct GNUNET_CURL_Context *ctx, "%sprivate/", backend_url); return kyc_get (ctx, - url, + url, /* consumed! */ h_wire, exchange_url, timeout, @@ -345,7 +379,7 @@ TALER_MERCHANT_management_kyc_get (struct GNUNET_CURL_Context *ctx, backend_url, instance_id); return kyc_get (ctx, - url, + url, /* consumed! */ h_wire, exchange_url, timeout, diff --git a/src/lib/merchant_api_get_orders.c b/src/lib/merchant_api_get_orders.c index 817c9240..459409fd 100644 --- a/src/lib/merchant_api_get_orders.c +++ b/src/lib/merchant_api_get_orders.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018, 2020, 2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -30,6 +30,10 @@ #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> +/** + * Maximum number of orders we return. + */ +#define MAX_ORDERS 1024 /** * Handle for a GET /orders operation. @@ -68,65 +72,64 @@ struct TALER_MERCHANT_OrdersGetHandle * Parse order information from @a ia. * * @param ia JSON array (or NULL!) with order data + * @param[in] ogr response to fill * @param ogh operation handle * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue parse_orders (const json_t *ia, + struct TALER_MERCHANT_OrdersGetResponse *ogr, struct TALER_MERCHANT_OrdersGetHandle *ogh) { - unsigned int oes_len = json_array_size (ia); - struct TALER_MERCHANT_OrderEntry oes[oes_len]; - size_t index; - json_t *value; - int ret; - - ret = GNUNET_OK; - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_OrderEntry *ie = &oes[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("order_id", - &ie->order_id), - GNUNET_JSON_spec_timestamp ("timestamp", - &ie->timestamp), - GNUNET_JSON_spec_uint64 ("row_id", - &ie->order_serial), - TALER_JSON_spec_amount_any ("amount", - &ie->amount), - GNUNET_JSON_spec_string ("summary", - &ie->summary), - GNUNET_JSON_spec_bool ("refundable", - &ie->refundable), - GNUNET_JSON_spec_bool ("paid", - &ie->paid), - GNUNET_JSON_spec_end () - }; + unsigned int oes_len = (unsigned int) json_array_size (ia); - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - continue; - } - if (GNUNET_SYSERR == ret) - break; + if ( (json_array_size (ia) != (size_t) oes_len) || + (oes_len > MAX_ORDERS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; } - if (GNUNET_OK == ret) { - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = MHD_HTTP_OK - }; + struct TALER_MERCHANT_OrderEntry oes[GNUNET_NZL (oes_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_OrderEntry *ie = &oes[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("order_id", + &ie->order_id), + GNUNET_JSON_spec_timestamp ("timestamp", + &ie->timestamp), + GNUNET_JSON_spec_uint64 ("row_id", + &ie->order_serial), + TALER_JSON_spec_amount_any ("amount", + &ie->amount), + GNUNET_JSON_spec_string ("summary", + &ie->summary), + GNUNET_JSON_spec_bool ("refundable", + &ie->refundable), + GNUNET_JSON_spec_bool ("paid", + &ie->paid), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + ogr->details.ok.orders_length = oes_len; + ogr->details.ok.orders = oes; ogh->cb (ogh->cb_cls, - &hr, - oes_len, - oes); + ogr); ogh->cb = NULL; /* just to be sure */ } - return ret; + return GNUNET_OK; } @@ -145,9 +148,9 @@ handle_get_orders_finished (void *cls, { struct TALER_MERCHANT_OrdersGetHandle *ogh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_OrdersGetResponse ogr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; ogh->job = NULL; @@ -158,10 +161,10 @@ handle_get_orders_finished (void *cls, { case MHD_HTTP_OK: { - json_t *orders; + const json_t *orders; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("orders", - &orders), + GNUNET_JSON_spec_array_const ("orders", + &orders), GNUNET_JSON_spec_end () }; @@ -170,52 +173,43 @@ handle_get_orders_finished (void *cls, spec, NULL, NULL)) { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + ogr.hr.http_status = 0; + ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } - else + if (GNUNET_OK == + parse_orders (orders, + &ogr, + ogh)) { - if ( (! json_is_array (orders)) || - (GNUNET_OK == - parse_orders (orders, - ogh)) ) - { - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_orders_get_cancel (ogh); - return; - } - else - { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } + TALER_MERCHANT_orders_get_cancel (ogh); + return; } - GNUNET_JSON_parse_free (spec); + ogr.hr.http_status = 0; + ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ogr.hr.ec = TALER_JSON_get_error_code (json); + ogr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ogr.hr.ec = TALER_JSON_get_error_code (json); + ogr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ogr.hr.ec = TALER_JSON_get_error_code (json); + ogr.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) hr.ec); + (int) ogr.hr.ec); break; } ogh->cb (ogh->cb_cls, - &hr, - 0, - NULL); + &ogr); TALER_MERCHANT_orders_get_cancel (ogh); } @@ -255,12 +249,51 @@ TALER_MERCHANT_orders_get2 ( TALER_MERCHANT_OrdersGetCallback cb, void *cb_cls) { + return TALER_MERCHANT_orders_get3 ( + ctx, + backend_url, + paid, + refunded, + wired, + NULL, + NULL, + date, + start_row, + delta, + timeout, + cb, + cb_cls); +} + + +struct TALER_MERCHANT_OrdersGetHandle * +TALER_MERCHANT_orders_get3 ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + enum TALER_EXCHANGE_YesNoAll paid, + enum TALER_EXCHANGE_YesNoAll refunded, + enum TALER_EXCHANGE_YesNoAll wired, + const char *session_id, + const char *fulfillment_url, + struct GNUNET_TIME_Timestamp date, + uint64_t start_row, + int64_t delta, + struct GNUNET_TIME_Relative timeout, + TALER_MERCHANT_OrdersGetCallback cb, + void *cb_cls) +{ struct TALER_MERCHANT_OrdersGetHandle *ogh; CURL *eh; - unsigned int timeout_ms = timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + unsigned int tms = timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; GNUNET_assert (NULL != backend_url); + if ( (delta > MAX_ORDERS) || + (delta < -MAX_ORDERS) ) + { + GNUNET_break (0); + return NULL; + } if (0 == delta) { GNUNET_break (0); @@ -273,7 +306,9 @@ TALER_MERCHANT_orders_get2 ( /* build ogh->url with the various optional arguments */ { - char dstr[30]; + char *dstr; + char *fec = NULL; + char *sid = NULL; bool have_date; bool have_srow; char cbuf[30]; @@ -282,8 +317,8 @@ TALER_MERCHANT_orders_get2 ( GNUNET_snprintf (tbuf, sizeof (tbuf), - "%llu", - (unsigned long long) timeout_ms); + "%u", + tms); GNUNET_snprintf (dbuf, sizeof (dbuf), "%lld", @@ -292,13 +327,15 @@ TALER_MERCHANT_orders_get2 ( sizeof (cbuf), "%llu", (unsigned long long) start_row); - // FIXME: use date_s, no need for milliseconds! - GNUNET_snprintf (dstr, - sizeof (dstr), - "%llu", - (unsigned long long) (date.abs_time.abs_value_us - / GNUNET_TIME_UNIT_MILLISECONDS. - rel_value_us)); + if (NULL != session_id) + (void) GNUNET_STRINGS_urlencode (strlen (session_id), + session_id, + &sid); + if (NULL != fulfillment_url) + (void) GNUNET_STRINGS_urlencode (strlen (fulfillment_url), + fulfillment_url, + &fec); + dstr = GNUNET_strdup (GNUNET_TIME_timestamp2s (date)); if (delta > 0) { have_date = ! GNUNET_TIME_absolute_is_zero (date.abs_time); @@ -323,7 +360,7 @@ TALER_MERCHANT_orders_get2 ( (TALER_EXCHANGE_YNA_ALL != wired) ? TALER_yna_to_string (wired) : NULL, - "date_ms", + "date_s", (have_date) ? dstr : NULL, @@ -336,10 +373,17 @@ TALER_MERCHANT_orders_get2 ( ? dbuf : NULL, "timeout_ms", - (0 != timeout_ms) + (0 != tms) ? tbuf : NULL, + "session_id", + sid, + "fulfillment_url", + fec, NULL); + GNUNET_free (dstr); + GNUNET_free (sid); + GNUNET_free (fec); } if (NULL == ogh->url) { @@ -352,6 +396,20 @@ TALER_MERCHANT_orders_get2 ( "Requesting URL '%s'\n", ogh->url); eh = TALER_MERCHANT_curl_easy_get_ (ogh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (ogh->url); + GNUNET_free (ogh); + return NULL; + } + if (0 != tms) + { + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); + } ogh->job = GNUNET_CURL_job_add (ctx, eh, &handle_get_orders_finished, diff --git a/src/lib/merchant_api_get_otp_device.c b/src/lib/merchant_api_get_otp_device.c new file mode 100644 index 00000000..65950af8 --- /dev/null +++ b/src/lib/merchant_api_get_otp_device.c @@ -0,0 +1,210 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_otp_device.c + * @brief Implementation of the GET /otp-devices/$ID request of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a GET /otp-devices/$ID operation. + */ +struct TALER_MERCHANT_OtpDeviceGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_OtpDeviceGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /otp-devices/$ID request. + * + * @param cls the `struct TALER_MERCHANT_OtpDeviceGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_otp_device_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_OtpDeviceGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_OtpDeviceGetResponse tgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /otp-devices/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + uint32_t alg32; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("otp_device_description", + &tgr.details.ok.otp_device_description), + GNUNET_JSON_spec_uint32 ("otp_algorithm", + &alg32), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint64 ("otp_ctr", + &tgr.details.ok.otp_ctr), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint64 ("otp_timestamp", + &tgr.details.ok.otp_timestamp_s), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("otp_code", + &tgr.details.ok.otp_code), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK == + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + tgr.details.ok.otp_alg = + (enum TALER_MerchantConfirmationAlgorithm) alg32; + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_otp_device_get_cancel (tgh); + return; + } + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.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) tgr.hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_otp_device_get_cancel (tgh); +} + + +struct TALER_MERCHANT_OtpDeviceGetHandle * +TALER_MERCHANT_otp_device_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *otp_device_id, + TALER_MERCHANT_OtpDeviceGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_OtpDeviceGetHandle *tgh; + CURL *eh; + + tgh = GNUNET_new (struct TALER_MERCHANT_OtpDeviceGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/otp-devices/%s", + otp_device_id); + tgh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_otp_device_finished, + tgh); + return tgh; +} + + +void +TALER_MERCHANT_otp_device_get_cancel ( + struct TALER_MERCHANT_OtpDeviceGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} diff --git a/src/lib/merchant_api_get_otp_devices.c b/src/lib/merchant_api_get_otp_devices.c new file mode 100644 index 00000000..4737944c --- /dev/null +++ b/src/lib/merchant_api_get_otp_devices.c @@ -0,0 +1,248 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_otp_devices.c + * @brief Implementation of the GET /otp-devices request of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + +/** + * Maximum number of OTP devices we return. + */ +#define MAX_OTP 1024 + + +/** + * Handle for a GET /otp-devices operation. + */ +struct TALER_MERCHANT_OtpDevicesGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_OtpDevicesGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Parse OTP device information from @a ia. + * + * @param ia JSON array (or NULL!) with otp_device data + * @param[in] tgr partially filled response + * @param tgh operation handle + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_otp_devices (const json_t *ia, + struct TALER_MERCHANT_OtpDevicesGetResponse *tgr, + struct TALER_MERCHANT_OtpDevicesGetHandle *tgh) +{ + unsigned int otp_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) otp_len) || + (otp_len > MAX_OTP) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_OtpDeviceEntry otp[GNUNET_NZL (otp_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_OtpDeviceEntry *ie = &otp[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("otp_device_id", + &ie->otp_device_id), + GNUNET_JSON_spec_string ("device_description", + &ie->device_description), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + tgr->details.ok.otp_devices_length = otp_len; + tgr->details.ok.otp_devices = otp; + tgh->cb (tgh->cb_cls, + tgr); + tgh->cb = NULL; /* just to be sure */ + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /otp-devices request. + * + * @param cls the `struct TALER_MERCHANT_OtpDevicesGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_otp_devices_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_OtpDevicesGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_OtpDevicesGetResponse tgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /otp-devices response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + const json_t *otp_devices; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("otp_devices", + &otp_devices), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + if (GNUNET_OK == + parse_otp_devices (otp_devices, + &tgr, + tgh)) + { + TALER_MERCHANT_otp_devices_get_cancel (tgh); + return; + } + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + default: + /* unexpected response code */ + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.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) tgr.hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_otp_devices_get_cancel (tgh); +} + + +struct TALER_MERCHANT_OtpDevicesGetHandle * +TALER_MERCHANT_otp_devices_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + TALER_MERCHANT_OtpDevicesGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_OtpDevicesGetHandle *tgh; + CURL *eh; + + tgh = GNUNET_new (struct TALER_MERCHANT_OtpDevicesGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + tgh->url = TALER_url_join (backend_url, + "private/otp-devices", + NULL); + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_otp_devices_finished, + tgh); + return tgh; +} + + +void +TALER_MERCHANT_otp_devices_get_cancel ( + struct TALER_MERCHANT_OtpDevicesGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} diff --git a/src/lib/merchant_api_get_product.c b/src/lib/merchant_api_get_product.c index 94d38ec7..bb10a438 100644 --- a/src/lib/merchant_api_get_product.c +++ b/src/lib/merchant_api_get_product.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018, 2020 Taler Systems SA + Copyright (C) 2014-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 @@ -79,9 +79,9 @@ handle_get_product_finished (void *cls, { struct TALER_MERCHANT_ProductGetHandle *pgh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_ProductGetResponse pgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; pgh->job = NULL; @@ -92,121 +92,87 @@ handle_get_product_finished (void *cls, { case MHD_HTTP_OK: { - const char *description; - json_t *description_i18n; - const char *unit; - struct TALER_Amount price; - const char *image; - json_t *taxes; - int64_t total_stock; - uint64_t total_sold; - uint64_t total_lost; - json_t *address; - bool rst_ok = true; - struct GNUNET_TIME_Timestamp next_restock - = GNUNET_TIME_UNIT_ZERO_TS; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("description", - &description), - GNUNET_JSON_spec_json ("description_i18n", - &description_i18n), - GNUNET_JSON_spec_string ("unit", - &unit), - TALER_JSON_spec_amount_any ("price", - &price), - GNUNET_JSON_spec_string ("image", - &image), - GNUNET_JSON_spec_json ("taxes", - &taxes), - GNUNET_JSON_spec_int64 ("total_stock", - &total_stock), - GNUNET_JSON_spec_uint64 ("total_sold", - &total_sold), - GNUNET_JSON_spec_uint64 ("total_lost", - &total_lost), - GNUNET_JSON_spec_json ("address", - &address), + GNUNET_JSON_spec_string ( + "description", + &pgr.details.ok.description), + GNUNET_JSON_spec_object_const ( + "description_i18n", + &pgr.details.ok.description_i18n), + GNUNET_JSON_spec_string ( + "unit", + &pgr.details.ok.unit), + TALER_JSON_spec_amount_any ( + "price", + &pgr.details.ok.price), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ( + "image", + &pgr.details.ok.image), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "taxes", + &pgr.details.ok.taxes), + NULL), + GNUNET_JSON_spec_int64 ( + "total_stock", + &pgr.details.ok.total_stock), + GNUNET_JSON_spec_uint64 ( + "total_sold", + &pgr.details.ok.total_sold), + GNUNET_JSON_spec_uint64 ( + "total_lost", + &pgr.details.ok.total_lost), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ( + "address", + &pgr.details.ok.location), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ( + "next_restock", + &pgr.details.ok.next_restock), + NULL), GNUNET_JSON_spec_end () }; - if (NULL != - json_object_get (json, - "next_restock")) - { - struct GNUNET_JSON_Specification spect[] = { - GNUNET_JSON_spec_timestamp ("next_restock", - &next_restock), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spect, - NULL, NULL)) - rst_ok = false; - } - - - if ( (rst_ok) && - (GNUNET_OK == - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) ) + if (GNUNET_OK == + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) { pgh->cb (pgh->cb_cls, - &hr, - description, - description_i18n, - unit, - &price, - image, - taxes, - total_stock, - total_sold, - total_lost, - address, - next_restock); + &pgr); GNUNET_JSON_parse_free (spec); TALER_MERCHANT_product_get_cancel (pgh); return; } - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - GNUNET_JSON_parse_free (spec); + pgr.hr.http_status = 0; + pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pgr.hr.ec = TALER_JSON_get_error_code (json); + pgr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pgr.hr.ec = TALER_JSON_get_error_code (json); + pgr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pgr.hr.ec = TALER_JSON_get_error_code (json); + pgr.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) hr.ec); + (int) pgr.hr.ec); break; } pgh->cb (pgh->cb_cls, - &hr, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - 0, - 0, - NULL, - GNUNET_TIME_UNIT_FOREVER_TS); + &pgr); TALER_MERCHANT_product_get_cancel (pgh); } diff --git a/src/lib/merchant_api_get_products.c b/src/lib/merchant_api_get_products.c index c3cc30e7..c33e24c9 100644 --- a/src/lib/merchant_api_get_products.c +++ b/src/lib/merchant_api_get_products.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2018, 2020 Taler Systems SA + Copyright (C) 2014-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 @@ -32,6 +32,12 @@ /** + * Maximum number of products we return. + */ +#define MAX_PRODUCTS 1024 + + +/** * Handle for a GET /products operation. */ struct TALER_MERCHANT_ProductsGetHandle @@ -67,54 +73,68 @@ struct TALER_MERCHANT_ProductsGetHandle /** * Parse product information from @a ia. * + * @param json overall JSON reply * @param ia JSON array (or NULL!) with product data * @param pgh operation handle * @return #GNUNET_OK on success */ -static int -parse_products (const json_t *ia, +static enum GNUNET_GenericReturnValue +parse_products (const json_t *json, + const json_t *ia, struct TALER_MERCHANT_ProductsGetHandle *pgh) { unsigned int ies_len = json_array_size (ia); - struct TALER_MERCHANT_InventoryEntry ies[ies_len]; - size_t index; - json_t *value; - int ret; - - ret = GNUNET_OK; - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_InventoryEntry *ie = &ies[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("product_id", - &ie->product_id), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - continue; - } - if (GNUNET_SYSERR == ret) - break; + + if ( (json_array_size (ia) != (size_t) ies_len) || + (ies_len > MAX_PRODUCTS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; } - if (GNUNET_OK == ret) { - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = MHD_HTTP_OK - }; - - pgh->cb (pgh->cb_cls, - &hr, - ies_len, - ies); - pgh->cb = NULL; /* just to be sure */ + struct TALER_MERCHANT_InventoryEntry ies[GNUNET_NZL (ies_len)]; + size_t index; + json_t *value; + enum GNUNET_GenericReturnValue ret; + + ret = GNUNET_OK; + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_InventoryEntry *ie = &ies[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("product_id", + &ie->product_id), + GNUNET_JSON_spec_uint64 ("product_serial", + &ie->product_serial), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + continue; + } + if (GNUNET_SYSERR == ret) + break; + } + if (GNUNET_OK == ret) + { + struct TALER_MERCHANT_GetProductsResponse gpr = { + .hr.http_status = MHD_HTTP_OK, + .hr.reply = json, + .details.ok.products_length = ies_len, + .details.ok.products = ies + }; + + pgh->cb (pgh->cb_cls, + &gpr); + pgh->cb = NULL; /* just to be sure */ + } + return ret; } - return ret; } @@ -133,9 +153,9 @@ handle_get_products_finished (void *cls, { struct TALER_MERCHANT_ProductsGetHandle *pgh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_GetProductsResponse gpr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; pgh->job = NULL; @@ -146,10 +166,10 @@ handle_get_products_finished (void *cls, { case MHD_HTTP_OK: { - json_t *products; + const json_t *products; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("products", - &products), + GNUNET_JSON_spec_array_const ("products", + &products), GNUNET_JSON_spec_end () }; @@ -158,48 +178,39 @@ handle_get_products_finished (void *cls, spec, NULL, NULL)) { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + gpr.hr.http_status = 0; + gpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; } - else + if (GNUNET_OK == + parse_products (json, + products, + pgh)) { - if ( (! json_is_array (products)) || - (GNUNET_OK == - parse_products (products, - pgh)) ) - { - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_products_get_cancel (pgh); - return; - } - else - { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } + TALER_MERCHANT_products_get_cancel (pgh); + return; } - GNUNET_JSON_parse_free (spec); + gpr.hr.http_status = 0; + gpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + gpr.hr.ec = TALER_JSON_get_error_code (json); + gpr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; default: /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + gpr.hr.ec = TALER_JSON_get_error_code (json); + gpr.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) hr.ec); + (int) gpr.hr.ec); break; } pgh->cb (pgh->cb_cls, - &hr, - 0, - NULL); + &gpr); TALER_MERCHANT_products_get_cancel (pgh); } diff --git a/src/lib/merchant_api_get_reserve.c b/src/lib/merchant_api_get_reserve.c deleted file mode 100644 index cc6bc562..00000000 --- a/src/lib/merchant_api_get_reserve.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2021 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_reserve.c - * @brief Implementation of the GET /reserve request of the merchant's HTTP API - * @author Marcello Stanisci - * @author Christian Grothoff - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> - - -/** - * @brief A Handle for tracking wire reserve. - */ -struct TALER_MERCHANT_ReserveGetHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_ReserveGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP GET /reserve request. - * - * @param cls the `struct TALER_MERCHANT_ReserveGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_reserve_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_ReserveGetHandle *rgh = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - bool active; - - rgh->job = NULL; - switch (response_code) - { - case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct TALER_MERCHANT_ReserveSummary rs; - const json_t *tips; - const char *exchange_url = NULL; - const char *payto_uri = NULL; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("creation_time", - &rs.creation_time), - GNUNET_JSON_spec_timestamp ("expiration_time", - &rs.expiration_time), - GNUNET_JSON_spec_bool ("active", - &active), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("exchange_url", - &exchange_url), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("payto_uri", - &payto_uri), - NULL), - TALER_JSON_spec_amount_any ("merchant_initial_amount", - &rs.merchant_initial_amount), - TALER_JSON_spec_amount_any ("exchange_initial_amount", - &rs.exchange_initial_amount), - TALER_JSON_spec_amount_any ("pickup_amount", - &rs.pickup_amount), - TALER_JSON_spec_amount_any ("committed_amount", - &rs.committed_amount), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - - tips = json_object_get (json, - "tips"); - if ((NULL == tips) || - json_is_null (tips)) - { - rgh->cb (rgh->cb_cls, - &hr, - &rs, - false, - NULL, - NULL, - 0, - NULL); - TALER_MERCHANT_reserve_get_cancel (rgh); - return; - } - if (! json_is_array (tips)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - { - size_t tds_length; - json_t *tip; - struct TALER_MERCHANT_TipDetails *tds; - unsigned int i; - bool ok; - - tds_length = json_array_size (tips); - tds = GNUNET_new_array (tds_length, - struct TALER_MERCHANT_TipDetails); - ok = true; - json_array_foreach (tips, i, tip) { - struct TALER_MERCHANT_TipDetails *td = &tds[i]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_fixed_auto ("tip_id", - &td->tip_id), - TALER_JSON_spec_amount_any ("total_amount", - &td->amount), - GNUNET_JSON_spec_string ("reason", - &td->reason), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (tip, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - ok = false; - break; - } - } - - if (! ok) - { - GNUNET_break_op (0); - GNUNET_free (tds); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - rgh->cb (rgh->cb_cls, - &hr, - &rs, - active, - exchange_url, - payto_uri, - tds_length, - tds); - GNUNET_free (tds); - TALER_MERCHANT_reserve_get_cancel (rgh); - return; - } - } - case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - response_code = 0; - break; - } - rgh->cb (rgh->cb_cls, - &hr, - NULL, - false, - NULL, - NULL, - 0, - NULL); - TALER_MERCHANT_reserve_get_cancel (rgh); -} - - -struct TALER_MERCHANT_ReserveGetHandle * -TALER_MERCHANT_reserve_get (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_ReservePublicKeyP *reserve_pub, - bool fetch_tips, - TALER_MERCHANT_ReserveGetCallback cb, - void *cb_cls) -{ - struct TALER_MERCHANT_ReserveGetHandle *rgh; - CURL *eh; - - rgh = GNUNET_new (struct TALER_MERCHANT_ReserveGetHandle); - rgh->ctx = ctx; - rgh->cb = cb; - rgh->cb_cls = cb_cls; - { - char res_str[sizeof (*reserve_pub) * 2]; - char arg_str[sizeof (res_str) + 32]; - char *end; - - end = GNUNET_STRINGS_data_to_string (reserve_pub, - sizeof (*reserve_pub), - res_str, - sizeof (res_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "private/reserves/%s", - res_str); - rgh->url = TALER_url_join (backend_url, - arg_str, - "tips", - fetch_tips ? "yes" : "no", - NULL); - } - if (NULL == rgh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (rgh); - return NULL; - } - eh = TALER_MERCHANT_curl_easy_get_ (rgh->url); - rgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_reserve_get_finished, - rgh); - return rgh; -} - - -void -TALER_MERCHANT_reserve_get_cancel ( - struct TALER_MERCHANT_ReserveGetHandle *rgh) -{ - if (NULL != rgh->job) - { - GNUNET_CURL_job_cancel (rgh->job); - rgh->job = NULL; - } - GNUNET_free (rgh->url); - GNUNET_free (rgh); -} - - -/* end of merchant_api_get_reserve.c */ diff --git a/src/lib/merchant_api_get_reserves.c b/src/lib/merchant_api_get_reserves.c deleted file mode 100644 index 24a527d6..00000000 --- a/src/lib/merchant_api_get_reserves.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2017, 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 - <http://www.gnu.org/licenses/> -*/ -/** - * @file merchant_api_get_reserves.c - * @brief Implementation of the GET /reserves request of the merchant's HTTP API - * @author Marcello Stanisci - * @author Christian Grothoff - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> - - -/** - * @brief A Handle for tracking wire reserves. - */ -struct TALER_MERCHANT_ReservesGetHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_ReservesGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP GET /reserves request. - * - * @param cls the `struct TALER_MERCHANT_ReservesGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_reserves_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_ReservesGetHandle *rgh = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - rgh->job = NULL; - switch (response_code) - { - case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - json_t *reserves; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("reserves", - &reserves), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - else - { - size_t rds_length; - struct TALER_MERCHANT_ReserveSummary *rds; - json_t *reserve; - unsigned int i; - bool ok; - - if (! json_is_array (reserves)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - rds_length = json_array_size (reserves); - rds = GNUNET_new_array (rds_length, - struct TALER_MERCHANT_ReserveSummary); - ok = true; - json_array_foreach (reserves, i, reserve) { - struct TALER_MERCHANT_ReserveSummary *rd = &rds[i]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &rd->reserve_pub), - GNUNET_JSON_spec_timestamp ("creation_time", - &rd->creation_time), - GNUNET_JSON_spec_timestamp ("expiration_time", - &rd->expiration_time), - TALER_JSON_spec_amount_any ("merchant_initial_amount", - &rd->merchant_initial_amount), - TALER_JSON_spec_amount_any ("exchange_initial_amount", - &rd->exchange_initial_amount), - TALER_JSON_spec_amount_any ("pickup_amount", - &rd->pickup_amount), - TALER_JSON_spec_amount_any ("committed_amount", - &rd->committed_amount), - GNUNET_JSON_spec_bool ("active", - &rd->active), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (reserve, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - ok = false; - break; - } - } - - if (! ok) - { - GNUNET_break_op (0); - GNUNET_free (rds); - GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - rgh->cb (rgh->cb_cls, - &hr, - rds_length, - rds); - GNUNET_free (rds); - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_reserves_get_cancel (rgh); - return; - } - } - case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - response_code = 0; - break; - } - rgh->cb (rgh->cb_cls, - &hr, - 0, - NULL); - TALER_MERCHANT_reserves_get_cancel (rgh); -} - - -struct TALER_MERCHANT_ReservesGetHandle * -TALER_MERCHANT_reserves_get (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - struct GNUNET_TIME_Timestamp after, - enum TALER_EXCHANGE_YesNoAll active, - enum TALER_EXCHANGE_YesNoAll failures, - TALER_MERCHANT_ReservesGetCallback cb, - void *cb_cls) -{ - struct TALER_MERCHANT_ReservesGetHandle *rgh; - CURL *eh; - const char *active_s = NULL; - const char *failures_s = NULL; - char *after_s; - - rgh = GNUNET_new (struct TALER_MERCHANT_ReservesGetHandle); - rgh->ctx = ctx; - rgh->cb = cb; - rgh->cb_cls = cb_cls; - active_s = TALER_yna_to_string (active); - failures_s = TALER_yna_to_string (failures); - // FIXME: use different format? - after_s = GNUNET_strdup (GNUNET_TIME_timestamp2s (after)); - rgh->url = TALER_url_join (backend_url, - "private/reserves", - "active", - active_s, - "failures", - failures_s, - "after", - GNUNET_TIME_absolute_is_zero (after.abs_time) - ? NULL - : after_s, - NULL); - GNUNET_free (after_s); - if (NULL == rgh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (rgh); - return NULL; - } - eh = TALER_MERCHANT_curl_easy_get_ (rgh->url); - rgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_reserves_get_finished, - rgh); - return rgh; -} - - -void -TALER_MERCHANT_reserves_get_cancel ( - struct TALER_MERCHANT_ReservesGetHandle *rgh) -{ - if (NULL != rgh->job) - { - GNUNET_CURL_job_cancel (rgh->job); - rgh->job = NULL; - } - GNUNET_free (rgh->url); - GNUNET_free (rgh); -} - - -/* end of merchant_api_get_reserves.c */ diff --git a/src/lib/merchant_api_get_template.c b/src/lib/merchant_api_get_template.c new file mode 100644 index 00000000..9bbcc93a --- /dev/null +++ b/src/lib/merchant_api_get_template.c @@ -0,0 +1,201 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_template.c + * @brief Implementation of the GET /templates/$ID request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a GET /templates/$ID operation. + */ +struct TALER_MERCHANT_TemplateGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TemplateGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /templates/$ID request. + * + * @param cls the `struct TALER_MERCHANT_TemplateGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_template_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TemplateGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_TemplateGetResponse tgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /templates/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + const json_t *contract; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("template_description", + &tgr.details.ok.template_description), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("otp_id", + &tgr.details.ok.otp_id), + NULL), + GNUNET_JSON_spec_object_const ("template_contract", + &contract), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK == + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + tgr.details.ok.template_contract = contract; + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_template_get_cancel (tgh); + return; + } + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.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) tgr.hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_template_get_cancel (tgh); +} + + +struct TALER_MERCHANT_TemplateGetHandle * +TALER_MERCHANT_template_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *template_id, + TALER_MERCHANT_TemplateGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TemplateGetHandle *tgh; + CURL *eh; + + tgh = GNUNET_new (struct TALER_MERCHANT_TemplateGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/templates/%s", + template_id); + tgh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_template_finished, + tgh); + return tgh; +} + + +void +TALER_MERCHANT_template_get_cancel ( + struct TALER_MERCHANT_TemplateGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} diff --git a/src/lib/merchant_api_get_templates.c b/src/lib/merchant_api_get_templates.c new file mode 100644 index 00000000..f1f973b5 --- /dev/null +++ b/src/lib/merchant_api_get_templates.c @@ -0,0 +1,247 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_templates.c + * @brief Implementation of the GET /templates request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Maximum number of templates we return. + */ +#define MAX_TEMPLATES 1024 + + +/** + * Handle for a GET /templates operation. + */ +struct TALER_MERCHANT_TemplatesGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TemplatesGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Parse template information from @a ia. + * + * @param ia JSON array (or NULL!) with template data + * @param[in] tgr partially filled response + * @param tgh operation handle + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_templates (const json_t *ia, + struct TALER_MERCHANT_TemplatesGetResponse *tgr, + struct TALER_MERCHANT_TemplatesGetHandle *tgh) +{ + unsigned int tmpl_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) tmpl_len) || + (tmpl_len > MAX_TEMPLATES) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_TemplateEntry tmpl[GNUNET_NZL (tmpl_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_TemplateEntry *ie = &tmpl[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("template_id", + &ie->template_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + tgr->details.ok.templates_length = tmpl_len; + tgr->details.ok.templates = tmpl; + tgh->cb (tgh->cb_cls, + tgr); + tgh->cb = NULL; /* just to be sure */ + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /templates request. + * + * @param cls the `struct TALER_MERCHANT_TemplatesGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_templates_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TemplatesGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_TemplatesGetResponse tgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /templates response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + const json_t *templates; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("templates", + &templates), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + if (GNUNET_OK == + parse_templates (templates, + &tgr, + tgh)) + { + TALER_MERCHANT_templates_get_cancel (tgh); + return; + } + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + default: + /* unexpected response code */ + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.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) tgr.hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_templates_get_cancel (tgh); +} + + +struct TALER_MERCHANT_TemplatesGetHandle * +TALER_MERCHANT_templates_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + TALER_MERCHANT_TemplatesGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TemplatesGetHandle *tgh; + CURL *eh; + + tgh = GNUNET_new (struct TALER_MERCHANT_TemplatesGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + tgh->url = TALER_url_join (backend_url, + "private/templates", + NULL); + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_templates_finished, + tgh); + return tgh; +} + + +void +TALER_MERCHANT_templates_get_cancel ( + struct TALER_MERCHANT_TemplatesGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} diff --git a/src/lib/merchant_api_get_tips.c b/src/lib/merchant_api_get_tips.c deleted file mode 100644 index 7af6936b..00000000 --- a/src/lib/merchant_api_get_tips.c +++ /dev/null @@ -1,307 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 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 - <http://www.gnu.org/licenses/> -*/ -/** - * @file merchant_api_get_tips.c - * @brief Implementation of the GET /private/tips request of the merchant's HTTP API - * @author Jonathan Buchanan - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> - - -/** - * Handle for a GET /private/tips operation. - */ -struct TALER_MERCHANT_TipsGetHandle -{ - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_TipsGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - -}; - - -/** - * Parse tip information from @a ia. - * - * @param ia JSON array (or NULL!) tip order data - * @param tgh operation handle - * @return #GNUNET_OK on success - */ -static int -parse_tips (const json_t *ia, - struct TALER_MERCHANT_TipsGetHandle *tgh) -{ - unsigned int tes_len = json_array_size (ia); - struct TALER_MERCHANT_TipEntry tes[tes_len]; - size_t index; - json_t *value; - int ret; - - ret = GNUNET_OK; - json_array_foreach (ia, index, value) { - struct TALER_MERCHANT_TipEntry *ie = &tes[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("row_id", - &ie->row_id), - GNUNET_JSON_spec_fixed_auto ("tip_id", - &ie->tip_id), - TALER_JSON_spec_amount_any ("tip_amount", - &ie->tip_amount), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - continue; - } - if (GNUNET_SYSERR == ret) - break; - } - if (GNUNET_OK == ret) - { - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = MHD_HTTP_OK - }; - - tgh->cb (tgh->cb_cls, - &hr, - tes_len, - tes); - tgh->cb = NULL; /* just to be sure */ - } - return ret; -} - - -/** - * Function called when we're done processing the - * HTTP GET /private/tips request. - * - * @param cls the `struct TALER_MERCHANT_TipsGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_get_tips_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_TipsGetHandle *tgh = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - tgh->job = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Got /private/tips response with status code %u\n", - (unsigned int) response_code); - switch (response_code) - { - case MHD_HTTP_OK: - { - json_t *tips; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("tips", - &tips), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } - else - { - if ( (! json_is_array (tips)) || - (GNUNET_OK == - parse_tips (tips, - tgh)) ) - { - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_tips_get_cancel (tgh); - return; - } - else - { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } - } - GNUNET_JSON_parse_free (spec); - break; - } - case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - default: - /* unexpected response code */ - hr.ec = TALER_JSON_get_error_code (json); - 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) hr.ec); - break; - } - tgh->cb (tgh->cb_cls, - &hr, - 0, - NULL); - TALER_MERCHANT_tips_get_cancel (tgh); -} - - -struct TALER_MERCHANT_TipsGetHandle * -TALER_MERCHANT_tips_get ( - struct GNUNET_CURL_Context *ctx, - const char *backend_url, - TALER_MERCHANT_TipsGetCallback cb, - void *cb_cls) -{ - return TALER_MERCHANT_tips_get2 (ctx, - backend_url, - TALER_EXCHANGE_YNA_NO, - -20, - UINT64_MAX, - cb, - cb_cls); -} - - -struct TALER_MERCHANT_TipsGetHandle * -TALER_MERCHANT_tips_get2 (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - enum TALER_EXCHANGE_YesNoAll expired, - int64_t limit, - uint64_t offset, - TALER_MERCHANT_TipsGetCallback cb, - void *cb_cls) -{ - struct TALER_MERCHANT_TipsGetHandle *tgh; - CURL *eh; - - GNUNET_assert (NULL != backend_url); - if (0 == limit) - { - GNUNET_break (0); - return NULL; - } - tgh = GNUNET_new (struct TALER_MERCHANT_TipsGetHandle); - tgh->ctx = ctx; - tgh->cb = cb; - tgh->cb_cls = cb_cls; - - /* build tgh->url with the various optional arguments */ - { - char cbuf[30]; - char lbuf[30]; - bool have_offset; - - GNUNET_snprintf (lbuf, - sizeof (lbuf), - "%lld", - (long long) limit); - - if (limit > 0) - have_offset = (0 != offset); - else - have_offset = (UINT64_MAX != offset); - GNUNET_snprintf (cbuf, - sizeof (cbuf), - "%llu", - (unsigned long long) offset); - tgh->url = TALER_url_join (backend_url, - "private/tips", - "expired", - (TALER_EXCHANGE_YNA_ALL != expired) - ? TALER_yna_to_string (expired) - : NULL, - "offset", (have_offset) ? cbuf : NULL, - "limit", (-20 != limit) ? lbuf : NULL, - NULL); - } - if (NULL == tgh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (tgh); - return NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - tgh->url); - eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); - tgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_get_tips_finished, - tgh); - return tgh; -} - - -void -TALER_MERCHANT_tips_get_cancel ( - struct TALER_MERCHANT_TipsGetHandle *tgh) -{ - if (NULL != tgh->job) - GNUNET_CURL_job_cancel (tgh->job); - GNUNET_free (tgh->url); - GNUNET_free (tgh); -} diff --git a/src/lib/merchant_api_get_tokenfamily.c b/src/lib/merchant_api_get_tokenfamily.c new file mode 100644 index 00000000..d7e6b06e --- /dev/null +++ b/src/lib/merchant_api_get_tokenfamily.c @@ -0,0 +1,210 @@ +/* + This file is part of TALER + Copyright (C) 2014-2023 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_tokenfamily.c + * @brief Implementation of the GET /tokenfamily/$ID request of the merchant's HTTP API + * @author Christian Blättler + */ +#include "platform.h" +#include <curl/curl.h> +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a GET /tokenfamilies/$SLUG operation. + */ +struct TALER_MERCHANT_TokenFamilyGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TokenFamilyGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /tokenfamilies/$ID request. + * + * @param cls the `struct TALER_MERCHANT_TokenFamilyGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_token_family_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TokenFamilyGetHandle *handle = cls; + const json_t *json = response; + struct TALER_MERCHANT_TokenFamilyGetResponse res = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + handle->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /tokenfamilies/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + // Parse token family response + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("slug", + &res.details.ok.slug), + GNUNET_JSON_spec_string ("name", + &res.details.ok.name), + GNUNET_JSON_spec_string ("description", + &res.details.ok.description), + GNUNET_JSON_spec_object_const ("description_i18n", + &res.details.ok.description_i18n), + GNUNET_JSON_spec_timestamp ("valid_after", + &res.details.ok.valid_after), + GNUNET_JSON_spec_timestamp ("valid_before", + &res.details.ok.valid_before), + GNUNET_JSON_spec_relative_time ("duation", + &res.details.ok.duration), + GNUNET_JSON_spec_string ("kind", + &res.details.ok.kind), + GNUNET_JSON_spec_uint64 ("issued", + &res.details.ok.issued), + GNUNET_JSON_spec_uint64 ("redeemed", + &res.details.ok.redeemed), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK == + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + handle->cb (handle->cb_cls, + &res); + GNUNET_JSON_parse_free (spec); + TALER_MERCHANT_token_family_get_cancel (handle); + return; + } + res.hr.http_status = 0; + res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + res.hr.ec = TALER_JSON_get_error_code (json); + res.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + res.hr.ec = TALER_JSON_get_error_code (json); + res.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + res.hr.ec = TALER_JSON_get_error_code (json); + res.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) res.hr.ec); + break; + } +} + +struct TALER_MERCHANT_TokenFamilyGetHandle * +TALER_MERCHANT_token_family_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *token_family_slug, + TALER_MERCHANT_TokenFamilyGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TokenFamilyGetHandle *handle; + CURL *eh; + + handle = GNUNET_new (struct TALER_MERCHANT_TokenFamilyGetHandle); + handle->ctx = ctx; + handle->cb = cb; + handle->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/tokenfamilies/%s", + token_family_slug); + handle->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == handle->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (handle); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + handle->url); + eh = TALER_MERCHANT_curl_easy_get_ (handle->url); + handle->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_token_family_finished, + handle); + return handle; +} + +void +TALER_MERCHANT_token_family_get_cancel ( + struct TALER_MERCHANT_TokenFamilyGetHandle *handle) +{ + if (NULL != handle->job) + GNUNET_CURL_job_cancel (handle->job); + GNUNET_free (handle->url); + GNUNET_free (handle); +}
\ No newline at end of file diff --git a/src/lib/merchant_api_get_transfers.c b/src/lib/merchant_api_get_transfers.c index 8938c3d7..6116a54f 100644 --- a/src/lib/merchant_api_get_transfers.c +++ b/src/lib/merchant_api_get_transfers.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2017, 2020 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -27,6 +27,7 @@ #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> @@ -80,23 +81,23 @@ handle_transfers_get_finished (void *cls, { struct TALER_MERCHANT_GetTransfersHandle *gth = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_GetTransfersResponse gtr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; gth->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { - json_t *transfers; + const json_t *transfers; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("transfers", - &transfers), + GNUNET_JSON_spec_array_const ("transfers", + &transfers), GNUNET_JSON_spec_end () }; @@ -106,26 +107,18 @@ handle_transfers_get_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + gtr.hr.http_status = 0; + gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - else + { size_t tds_length; struct TALER_MERCHANT_TransferData *tds; json_t *transfer; - unsigned int i; + size_t i; bool ok; - if (! json_is_array (transfers)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } tds_length = json_array_size (transfers); tds = GNUNET_new_array (tds_length, struct TALER_MERCHANT_TransferData); @@ -137,9 +130,9 @@ handle_transfers_get_finished (void *cls, &td->credit_amount), GNUNET_JSON_spec_fixed_auto ("wtid", &td->wtid), - GNUNET_JSON_spec_string ("payto_uri", - &td->payto_uri), - GNUNET_JSON_spec_string ("exchange_url", + TALER_JSON_spec_payto_uri ("payto_uri", + &td->payto_uri), + TALER_JSON_spec_web_url ("exchange_url", &td->exchange_url), GNUNET_JSON_spec_uint64 ("transfer_serial_id", &td->credit_serial), @@ -173,55 +166,51 @@ handle_transfers_get_finished (void *cls, { GNUNET_break_op (0); GNUNET_free (tds); - GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + gtr.hr.http_status = 0; + gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } + gtr.details.ok.transfers = tds; + gtr.details.ok.transfers_length = tds_length; gth->cb (gth->cb_cls, - &hr, - tds_length, - tds); + >r); GNUNET_free (tds); - GNUNET_JSON_parse_free (spec); TALER_MERCHANT_transfers_get_cancel (gth); return; } } case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + gtr.hr.ec = TALER_JSON_get_error_code (json); + gtr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + gtr.hr.ec = TALER_JSON_get_error_code (json); + gtr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + gtr.hr.ec = TALER_JSON_get_error_code (json); + gtr.hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + >r.hr); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); - response_code = 0; + (int) gtr.hr.ec); + gtr.hr.http_status = 0; break; } gth->cb (gth->cb_cls, - &hr, - 0, - NULL); + >r); TALER_MERCHANT_transfers_get_cancel (gth); } @@ -260,7 +249,6 @@ TALER_MERCHANT_transfers_get ( sizeof (offset_s), "%lld", (unsigned long long) offset); - // FIXME: use other format? before_s = GNUNET_strdup (GNUNET_TIME_timestamp2s (before)); after_s = GNUNET_strdup (GNUNET_TIME_timestamp2s (after)); gth->url = TALER_url_join (backend_url, diff --git a/src/lib/merchant_api_get_webhook.c b/src/lib/merchant_api_get_webhook.c new file mode 100644 index 00000000..551aa915 --- /dev/null +++ b/src/lib/merchant_api_get_webhook.c @@ -0,0 +1,221 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_webhook.c + * @brief Implementation of the GET /webhooks/$ID request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a GET /webhooks/$ID operation. + */ +struct TALER_MERCHANT_WebhookGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_WebhookGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /webhooks/$ID request. + * + * @param cls the `struct TALER_MERCHANT_WebhookGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_webhook_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_WebhookGetHandle *wgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + wgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /webhooks/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + const char *event_type; + const char *url; + const char *http_method; + const char *header_template; + const char *body_template; + bool rst_ok = true; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("event_type", + &event_type), + TALER_JSON_spec_web_url ("url", + &url), + GNUNET_JSON_spec_string ("http_method", + &http_method), + GNUNET_JSON_spec_string ("header_template", + &header_template), + GNUNET_JSON_spec_string ("body_template", + &body_template), + GNUNET_JSON_spec_end () + }; + + + if ( (rst_ok) && + (GNUNET_OK == + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) ) + { + wgh->cb (wgh->cb_cls, + &hr, + event_type, + url, + http_method, + header_template, + body_template); + GNUNET_JSON_parse_free (spec); + TALER_MERCHANT_webhook_get_cancel (wgh); + return; + } + hr.http_status = 0; + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_JSON_parse_free (spec); + break; + } + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + hr.ec = TALER_JSON_get_error_code (json); + 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) hr.ec); + break; + } + wgh->cb (wgh->cb_cls, + &hr, + NULL, + NULL, + NULL, + NULL, + NULL); + TALER_MERCHANT_webhook_get_cancel (wgh); +} + + +struct TALER_MERCHANT_WebhookGetHandle * +TALER_MERCHANT_webhook_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *webhook_id, + TALER_MERCHANT_WebhookGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_WebhookGetHandle *wgh; + CURL *eh; + + wgh = GNUNET_new (struct TALER_MERCHANT_WebhookGetHandle); + wgh->ctx = ctx; + wgh->cb = cb; + wgh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/webhooks/%s", + webhook_id); + wgh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (wgh->url); + wgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_webhook_finished, + wgh); + return wgh; +} + + +void +TALER_MERCHANT_webhook_get_cancel ( + struct TALER_MERCHANT_WebhookGetHandle *wgh) +{ + if (NULL != wgh->job) + GNUNET_CURL_job_cancel (wgh->job); + GNUNET_free (wgh->url); + GNUNET_free (wgh); +} diff --git a/src/lib/merchant_api_get_webhooks.c b/src/lib/merchant_api_get_webhooks.c new file mode 100644 index 00000000..e702baac --- /dev/null +++ b/src/lib/merchant_api_get_webhooks.c @@ -0,0 +1,246 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_webhooks.c + * @brief Implementation of the GET /webhooks request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Maximum number of webhooks we return. + */ +#define MAX_WEBHOOKS 1024 + +/** + * Handle for a GET /webhooks operation. + */ +struct TALER_MERCHANT_WebhooksGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_WebhooksGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Parse webhook information from @a ia. + * + * @param ia JSON array (or NULL!) with webhook data + * @param[in] wgr partially filled webhook response + * @param wgh operation handle + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_webhooks (const json_t *ia, + struct TALER_MERCHANT_WebhooksGetResponse *wgr, + struct TALER_MERCHANT_WebhooksGetHandle *wgh) +{ + unsigned int whook_len = (unsigned int) json_array_size (ia); + + if ( (json_array_size (ia) != (size_t) whook_len) || + (whook_len > MAX_WEBHOOKS) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + { + struct TALER_MERCHANT_WebhookEntry whook[GNUNET_NZL (whook_len)]; + size_t index; + json_t *value; + + json_array_foreach (ia, index, value) { + struct TALER_MERCHANT_WebhookEntry *ie = &whook[index]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("webhook_id", + &ie->webhook_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + wgr->details.ok.webhooks_length = whook_len; + wgr->details.ok.webhooks = whook; + wgh->cb (wgh->cb_cls, + wgr); + wgh->cb = NULL; /* just to be sure */ + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /webhooks request. + * + * @param cls the `struct TALER_MERCHANT_WebhooksGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_webhooks_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_WebhooksGetHandle *wgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_WebhooksGetResponse wgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + wgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /webhooks response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + const json_t *webhooks; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("webhooks", + &webhooks), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + wgr.hr.http_status = 0; + wgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + if (GNUNET_OK == + parse_webhooks (webhooks, + &wgr, + wgh)) + { + TALER_MERCHANT_webhooks_get_cancel (wgh); + return; + } + wgr.hr.http_status = 0; + wgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + wgr.hr.ec = TALER_JSON_get_error_code (json); + wgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + default: + /* unexpected response code */ + wgr.hr.ec = TALER_JSON_get_error_code (json); + wgr.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) wgr.hr.ec); + break; + } + wgh->cb (wgh->cb_cls, + &wgr); + TALER_MERCHANT_webhooks_get_cancel (wgh); +} + + +struct TALER_MERCHANT_WebhooksGetHandle * +TALER_MERCHANT_webhooks_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + TALER_MERCHANT_WebhooksGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_WebhooksGetHandle *wgh; + CURL *eh; + + wgh = GNUNET_new (struct TALER_MERCHANT_WebhooksGetHandle); + wgh->ctx = ctx; + wgh->cb = cb; + wgh->cb_cls = cb_cls; + wgh->url = TALER_url_join (backend_url, + "private/webhooks", + NULL); + if (NULL == wgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (wgh->url); + wgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_webhooks_finished, + wgh); + return wgh; +} + + +void +TALER_MERCHANT_webhooks_get_cancel ( + struct TALER_MERCHANT_WebhooksGetHandle *wgh) +{ + if (NULL != wgh->job) + GNUNET_CURL_job_cancel (wgh->job); + GNUNET_free (wgh->url); + GNUNET_free (wgh); +} diff --git a/src/lib/merchant_api_lock_product.c b/src/lib/merchant_api_lock_product.c index de4da906..4f7c8be4 100644 --- a/src/lib/merchant_api_lock_product.c +++ b/src/lib/merchant_api_lock_product.c @@ -28,6 +28,7 @@ #include <microhttpd.h> /* just for HTTP status codes */ #include <gnunet/gnunet_util_lib.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> #include <taler/taler_curl_lib.h> diff --git a/src/lib/merchant_api_merchant_get_order.c b/src/lib/merchant_api_merchant_get_order.c index 167e46be..3bd4003b 100644 --- a/src/lib/merchant_api_merchant_get_order.c +++ b/src/lib/merchant_api_merchant_get_order.c @@ -34,6 +34,17 @@ /** + * Maximum number of refund details we return. + */ +#define MAX_REFUND_DETAILS 1024 + +/** + * Maximum number of wire details we return. + */ +#define MAX_WIRE_DETAILS 1024 + + +/** * @brief A GET /private/orders/$ORDER handle */ struct TALER_MERCHANT_OrderMerchantGetHandle @@ -72,47 +83,48 @@ struct TALER_MERCHANT_OrderMerchantGetHandle * the response and call the callback. * * @param omgh handle for the request - * @param[in,out] hr HTTP response we got + * @param[in,out] osr HTTP response we got */ static void handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, - struct TALER_MERCHANT_HttpResponse *hr) + struct TALER_MERCHANT_OrderStatusResponse *osr) { - struct TALER_MERCHANT_OrderStatusResponse osr = { - .status = TALER_MERCHANT_OSC_UNPAID - }; struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("total_amount", - &osr.details.unpaid.contract_amount), + TALER_JSON_spec_amount_any ( + "total_amount", + &osr->details.ok.details.unpaid.contract_amount), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("already_paid_order_id", - &osr.details.unpaid.already_paid_order_id), + GNUNET_JSON_spec_string ( + "already_paid_order_id", + &osr->details.ok.details.unpaid.already_paid_order_id), NULL), - GNUNET_JSON_spec_string ("taler_pay_uri", - &osr.details.unpaid.taler_pay_uri), - GNUNET_JSON_spec_string ("summary", - &osr.details.unpaid.summary), - GNUNET_JSON_spec_timestamp ("creation_time", - &osr.details.unpaid.creation_time), + GNUNET_JSON_spec_string ( + "taler_pay_uri", + &osr->details.ok.details.unpaid.taler_pay_uri), + GNUNET_JSON_spec_string ( + "summary", + &osr->details.ok.details.unpaid.summary), + GNUNET_JSON_spec_timestamp ( + "creation_time", + &osr->details.ok.details.unpaid.creation_time), GNUNET_JSON_spec_end () }; if (GNUNET_OK != - GNUNET_JSON_parse (hr->reply, + GNUNET_JSON_parse (osr->hr.reply, spec, NULL, NULL)) { GNUNET_break_op (0); - hr->http_status = 0; - hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED; + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, - hr, - NULL); + osr); return; } + osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID; omgh->cb (omgh->cb_cls, - hr, - &osr); + osr); } @@ -122,38 +134,34 @@ handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, * paid. Parse the response and call the callback. * * @param omgh handle for the request - * @param[in,out] hr HTTP response we got + * @param[in,out] osr HTTP response we got */ static void handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, - struct TALER_MERCHANT_HttpResponse *hr) + struct TALER_MERCHANT_OrderStatusResponse *osr) { - struct TALER_MERCHANT_OrderStatusResponse osr = { - .status = TALER_MERCHANT_OSC_CLAIMED - }; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("contract_terms", - (json_t **) &osr.details.claimed.contract_terms), + GNUNET_JSON_spec_object_const ( + "contract_terms", + &osr->details.ok.details.claimed.contract_terms), GNUNET_JSON_spec_end () }; if (GNUNET_OK != - GNUNET_JSON_parse (hr->reply, + GNUNET_JSON_parse (osr->hr.reply, spec, NULL, NULL)) { GNUNET_break_op (0); - hr->http_status = 0; - hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED; + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, - hr, - NULL); + osr); return; } + osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED; omgh->cb (omgh->cb_cls, - hr, - &osr); - GNUNET_JSON_parse_free (spec); + osr); } @@ -163,200 +171,158 @@ handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, * the response and call the callback. * * @param omgh handle for the request - * @param[in,out] hr HTTP response we got + * @param[in,out] osr HTTP response we got */ static void handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, - struct TALER_MERCHANT_HttpResponse *hr) + struct TALER_MERCHANT_OrderStatusResponse *osr) { - uint32_t ec32; uint32_t hc32; - json_t *wire_details; - json_t *wire_reports; - json_t *refund_details; - struct TALER_MERCHANT_OrderStatusResponse osr = { - .status = TALER_MERCHANT_OSC_PAID - }; + const json_t *wire_details; + const json_t *refund_details; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_bool ("refunded", - &osr.details.paid.refunded), + &osr->details.ok.details.paid.refunded), GNUNET_JSON_spec_bool ("refund_pending", - &osr.details.paid.refund_pending), + &osr->details.ok.details.paid.refund_pending), GNUNET_JSON_spec_bool ("wired", - &osr.details.paid.wired), + &osr->details.ok.details.paid.wired), TALER_JSON_spec_amount_any ("deposit_total", - &osr.details.paid.deposit_total), - GNUNET_JSON_spec_uint32 ("exchange_code", - &ec32), + &osr->details.ok.details.paid.deposit_total), + TALER_JSON_spec_ec ("exchange_code", + &osr->details.ok.details.paid.exchange_ec), GNUNET_JSON_spec_uint32 ("exchange_http_status", &hc32), TALER_JSON_spec_amount_any ("refund_amount", - &osr.details.paid.refund_amount), - GNUNET_JSON_spec_json ("contract_terms", - (json_t **) &osr.details.paid.contract_terms), - GNUNET_JSON_spec_json ("wire_details", - &wire_details), - GNUNET_JSON_spec_json ("wire_reports", - &wire_reports), - GNUNET_JSON_spec_json ("refund_details", - &refund_details), + &osr->details.ok.details.paid.refund_amount), + GNUNET_JSON_spec_object_const ( + "contract_terms", + &osr->details.ok.details.paid.contract_terms), + GNUNET_JSON_spec_array_const ("wire_details", + &wire_details), + GNUNET_JSON_spec_array_const ("refund_details", + &refund_details), + /* Only available since **v14** */ + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("last_payment", + &osr->details.ok.details.paid.last_payment), + NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != - GNUNET_JSON_parse (hr->reply, + GNUNET_JSON_parse (osr->hr.reply, spec, NULL, NULL)) { GNUNET_break_op (0); - hr->http_status = 0; - hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED; + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, - hr, - NULL); + osr); return; } - if (! (json_is_array (wire_details) && - json_is_array (wire_reports) && - json_is_array (refund_details) && - json_is_object (osr.details.paid.contract_terms)) ) - { - GNUNET_break_op (0); - hr->http_status = 0; - hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED; - omgh->cb (omgh->cb_cls, - hr, - NULL); - GNUNET_JSON_parse_free (spec); - return; - } - osr.details.paid.exchange_ec = (enum TALER_ErrorCode) ec32; - osr.details.paid.exchange_hc = (unsigned int) hc32; + osr->details.ok.status = TALER_MERCHANT_OSC_PAID; + + osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32; { - unsigned int wts_len = json_array_size (wire_details); - unsigned int wrs_len = json_array_size (wire_reports); - unsigned int ref_len = json_array_size (refund_details); - struct TALER_MERCHANT_WireTransfer wts[wts_len]; - struct TALER_MERCHANT_WireReport wrs[wrs_len]; - struct TALER_MERCHANT_RefundOrderDetail ref[ref_len]; - - for (unsigned int i = 0; i<wts_len; i++) + unsigned int wts_len = (unsigned int) json_array_size (wire_details); + unsigned int ref_len = (unsigned int) json_array_size (refund_details); + + if ( (json_array_size (wire_details) != (size_t) wts_len) || + (wts_len > MAX_WIRE_DETAILS) ) { - struct TALER_MERCHANT_WireTransfer *wt = &wts[i]; - const json_t *w = json_array_get (wire_details, - i); - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("exchange_url", - &wt->exchange_url), - GNUNET_JSON_spec_fixed_auto ("wtid", - &wt->wtid), - GNUNET_JSON_spec_timestamp ("execution_time", - &wt->execution_time), - TALER_JSON_spec_amount_any ("amount", - &wt->total_amount), - GNUNET_JSON_spec_bool ("confirmed", - &wt->confirmed), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (w, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - hr->http_status = 0; - hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED; - omgh->cb (omgh->cb_cls, - hr, - NULL); - GNUNET_JSON_parse_free (spec); - return; - } + GNUNET_break (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + omgh->cb (omgh->cb_cls, + osr); + return; + } + if ( (json_array_size (refund_details) != (size_t) ref_len) || + (ref_len > MAX_REFUND_DETAILS) ) + { + GNUNET_break (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + omgh->cb (omgh->cb_cls, + osr); + return; } - - for (unsigned int i = 0; i<wrs_len; i++) { - struct TALER_MERCHANT_WireReport *wr = &wrs[i]; - const json_t *w = json_array_get (wire_reports, i); - uint32_t c32; - uint32_t eec32; - uint32_t ehs32; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_uint32 ("code", - &c32), - GNUNET_JSON_spec_string ("hint", - &wr->hint), - GNUNET_JSON_spec_uint32 ("exchange_code", - &eec32), - GNUNET_JSON_spec_uint32 ("exchange_http_status", - &ehs32), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &wr->coin_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (w, - ispec, - NULL, NULL)) + struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)]; + struct TALER_MERCHANT_RefundOrderDetail ref[GNUNET_NZL (ref_len)]; + + for (unsigned int i = 0; i<wts_len; i++) { - GNUNET_break_op (0); - hr->http_status = 0; - hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED; - omgh->cb (omgh->cb_cls, - hr, - NULL); - GNUNET_JSON_parse_free (spec); - return; + struct TALER_MERCHANT_WireTransfer *wt = &wts[i]; + const json_t *w = json_array_get (wire_details, + i); + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_web_url ("exchange_url", + &wt->exchange_url), + GNUNET_JSON_spec_fixed_auto ("wtid", + &wt->wtid), + GNUNET_JSON_spec_timestamp ("execution_time", + &wt->execution_time), + TALER_JSON_spec_amount_any ("amount", + &wt->total_amount), + GNUNET_JSON_spec_bool ("confirmed", + &wt->confirmed), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (w, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + omgh->cb (omgh->cb_cls, + osr); + return; + } } - wr->code = (enum TALER_ErrorCode) c32; - wr->hr.ec = (enum TALER_ErrorCode) eec32; - wr->hr.http_status = (unsigned int) ehs32; - } - for (unsigned int i = 0; i<ref_len; i++) - { - struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i]; - const json_t *w = json_array_get (refund_details, - i); - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_amount_any ("amount", - &ro->refund_amount), - GNUNET_JSON_spec_string ("reason", - &ro->reason), - GNUNET_JSON_spec_timestamp ("timestamp", - &ro->refund_time), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (w, - ispec, - NULL, NULL)) + for (unsigned int i = 0; i<ref_len; i++) { - GNUNET_break_op (0); - hr->http_status = 0; - hr->ec = TALER_EC_GENERIC_REPLY_MALFORMED; - omgh->cb (omgh->cb_cls, - hr, - NULL); - GNUNET_JSON_parse_free (spec); - return; + struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i]; + const json_t *w = json_array_get (refund_details, + i); + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_amount_any ("amount", + &ro->refund_amount), + GNUNET_JSON_spec_string ("reason", + &ro->reason), + GNUNET_JSON_spec_timestamp ("timestamp", + &ro->refund_time), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (w, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + osr->hr.http_status = 0; + osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + omgh->cb (omgh->cb_cls, + osr); + return; + } } - } - osr.details.paid.wts = wts; - osr.details.paid.wts_len = wts_len; - osr.details.paid.wrs = wrs; - osr.details.paid.wrs_len = wrs_len; - osr.details.paid.refunds = ref; - osr.details.paid.refunds_len = ref_len; - omgh->cb (omgh->cb_cls, - hr, - &osr); + osr->details.ok.details.paid.wts = wts; + osr->details.ok.details.paid.wts_len = wts_len; + osr->details.ok.details.paid.refunds = ref; + osr->details.ok.details.paid.refunds_len = ref_len; + omgh->cb (omgh->cb_cls, + osr); + } } - GNUNET_JSON_parse_free (spec); } @@ -376,9 +342,9 @@ handle_merchant_order_get_finished (void *cls, struct TALER_MERCHANT_OrderMerchantGetHandle *omgh = cls; const json_t *json = response; const char *order_status; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_OrderStatusResponse osr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; omgh->job = NULL; @@ -387,106 +353,106 @@ handle_merchant_order_get_finished (void *cls, case MHD_HTTP_OK: /* see below */ break; + case MHD_HTTP_ACCEPTED: + /* see below */ + omgh->cb (omgh->cb_cls, + &osr); + TALER_MERCHANT_merchant_order_get_cancel (omgh); + return; case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; + osr.hr.ec = TALER_JSON_get_error_code (json); + osr.hr.hint = TALER_JSON_get_error_hint (json); + omgh->cb (omgh->cb_cls, + &osr); + TALER_MERCHANT_merchant_order_get_cancel (omgh); + return; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + osr.hr.ec = TALER_JSON_get_error_code (json); + osr.hr.hint = TALER_JSON_get_error_hint (json); omgh->cb (omgh->cb_cls, - &hr, - NULL); + &osr); TALER_MERCHANT_merchant_order_get_cancel (omgh); return; case MHD_HTTP_GATEWAY_TIMEOUT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + osr.hr.ec = TALER_JSON_get_error_code (json); + osr.hr.hint = TALER_JSON_get_error_hint (json); omgh->cb (omgh->cb_cls, - &hr, - NULL); + &osr); TALER_MERCHANT_merchant_order_get_cancel (omgh); return; default: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + osr.hr.ec = TALER_JSON_get_error_code (json); + osr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Polling payment failed with HTTP status code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) osr.hr.ec); GNUNET_break_op (0); omgh->cb (omgh->cb_cls, - &hr, - NULL); + &osr); TALER_MERCHANT_merchant_order_get_cancel (omgh); return; } - order_status = json_string_value (json_object_get (json, "order_status")); + order_status = json_string_value (json_object_get (json, + "order_status")); if (NULL == order_status) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + osr.hr.http_status = 0; + osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, - &hr, - NULL); + &osr); TALER_MERCHANT_merchant_order_get_cancel (omgh); return; } - if (0 == strcmp ("paid", order_status)) + if (0 == strcmp ("paid", + order_status)) { handle_paid (omgh, - &hr); + &osr); } - else if (0 == strcmp ("claimed", order_status)) + else if (0 == strcmp ("claimed", + order_status)) { handle_claimed (omgh, - &hr); + &osr); } - else if (0 == strcmp ("unpaid", order_status)) + else if (0 == strcmp ("unpaid", + order_status)) { handle_unpaid (omgh, - &hr); + &osr); } else { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + osr.hr.http_status = 0; + osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; omgh->cb (omgh->cb_cls, - &hr, - NULL); + &osr); } TALER_MERCHANT_merchant_order_get_cancel (omgh); } struct TALER_MERCHANT_OrderMerchantGetHandle * -TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const char *order_id, - const char *session_id, - bool transfer, - struct GNUNET_TIME_Relative timeout, - TALER_MERCHANT_OrderMerchantGetCallback cb, - void *cb_cls) +TALER_MERCHANT_merchant_order_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *order_id, + const char *session_id, + struct GNUNET_TIME_Relative timeout, + TALER_MERCHANT_OrderMerchantGetCallback cb, + void *cb_cls) { struct TALER_MERCHANT_OrderMerchantGetHandle *omgh; - unsigned long long tms; - long tlong; - - tms = (unsigned long long) (timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); - /* set curl timeout to *our* long poll timeout plus one minute - (for network latency and processing delays) */ - tlong = (long) (GNUNET_TIME_relative_add (timeout, - GNUNET_TIME_UNIT_MINUTES). - rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); + unsigned int tms; + + tms = (unsigned int) (timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle); omgh->ctx = ctx; omgh->cb = cb; @@ -497,7 +463,7 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx, GNUNET_snprintf (timeout_ms, sizeof (timeout_ms), - "%llu", + "%u", tms); GNUNET_asprintf (&path, "private/orders/%s", @@ -505,7 +471,6 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx, omgh->url = TALER_url_join (backend_url, path, "session_id", session_id, - "transfer", transfer ? "YES" : "NO", "timeout_ms", (0 != tms) ? timeout_ms : NULL, NULL); GNUNET_free (path); @@ -529,16 +494,12 @@ TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx, GNUNET_free (omgh); return NULL; } - if (CURLE_OK != - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - tlong)) + if (0 != tms) { - GNUNET_break (0); - curl_easy_cleanup (eh); - GNUNET_free (omgh->url); - GNUNET_free (omgh); - return NULL; + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, diff --git a/src/lib/merchant_api_merchant_get_tip.c b/src/lib/merchant_api_merchant_get_tip.c deleted file mode 100644 index 020167c4..00000000 --- a/src/lib/merchant_api_merchant_get_tip.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 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 - <http://www.gnu.org/licenses/> -*/ -/** - * @file merchant_api_merchant_get_tip.c - * @brief Implementation of the GET /private/tips/$TIP_ID request of the merchant's HTTP API - * @author Jonathan Buchanan - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> - - -struct TALER_MERCHANT_TipMerchantGetHandle -{ - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_TipMerchantGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -static int -parse_pickups (const json_t *pa, - const struct TALER_Amount *total_authorized, - const struct TALER_Amount *total_picked_up, - const char *reason, - struct GNUNET_TIME_Timestamp expiration, - const struct TALER_ReservePublicKeyP *reserve_pub, - struct TALER_MERCHANT_TipMerchantGetHandle *tgh) -{ - unsigned int pa_len = json_array_size (pa); - struct TALER_MERCHANT_PickupDetail pickups[pa_len]; - size_t index; - json_t *value; - int ret = GNUNET_OK; - - json_array_foreach (pa, index, value) - { - struct TALER_MERCHANT_PickupDetail *pickup = &pickups[index]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("pickup_id", - &pickup->pickup_id), - GNUNET_JSON_spec_uint64 ("num_planchets", - &pickup->num_planchets), - TALER_JSON_spec_amount_any ("requested_amount", - &pickup->requested_amount), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (value, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - break; - } - } - if (GNUNET_OK == ret) - { - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = MHD_HTTP_OK - }; - - tgh->cb (tgh->cb_cls, - &hr, - total_authorized, - total_picked_up, - reason, - expiration, - reserve_pub, - pa_len, - pickups); - } - return ret; -} - - -/** - * Function called when we're done processing the - * GET /private/tips/$TIP_ID request. - * - * @param cls the `struct TALER_MERCHANT_TipMerchantGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_merchant_tip_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_TipMerchantGetHandle *tgh = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Got /private/tips/$TIP_ID response with status code %u\n", - (unsigned int) response_code); - tgh->job = NULL; - switch (response_code) - { - case MHD_HTTP_OK: - { - struct TALER_Amount total_authorized; - struct TALER_Amount total_picked_up; - const char *reason; - struct GNUNET_TIME_Timestamp expiration; - struct TALER_ReservePublicKeyP reserve_pub; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("total_authorized", - &total_authorized), - TALER_JSON_spec_amount_any ("total_picked_up", - &total_picked_up), - GNUNET_JSON_spec_string ("reason", - &reason), - GNUNET_JSON_spec_timestamp ("expiration", - &expiration), - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &reserve_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } - else - { - json_t *pickups = json_object_get (json, - "pickups"); - if (! json_is_array (pickups)) - { - tgh->cb (tgh->cb_cls, - &hr, - &total_authorized, - &total_picked_up, - reason, - expiration, - &reserve_pub, - 0, - NULL); - TALER_MERCHANT_merchant_tip_get_cancel (tgh); - return; - } - else if (GNUNET_OK == parse_pickups (pickups, - &total_authorized, - &total_picked_up, - reason, - expiration, - &reserve_pub, - tgh)) - { - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_merchant_tip_get_cancel (tgh); - return; - } - else - { - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } - } - GNUNET_JSON_parse_free (spec); - break; - } - case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - case MHD_HTTP_NOT_FOUND: - /* legal, can happen if instance or tip reserve is unknown */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - break; - } - tgh->cb (tgh->cb_cls, - &hr, - NULL, - NULL, - NULL, - GNUNET_TIME_UNIT_ZERO_TS, - NULL, - 0, - NULL); - TALER_MERCHANT_merchant_tip_get_cancel (tgh); -} - - -struct TALER_MERCHANT_TipMerchantGetHandle * -TALER_MERCHANT_merchant_tip_get (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_TipIdentifierP *tip_id, - bool pickups, - TALER_MERCHANT_TipMerchantGetCallback cb, - void *cb_cls) -{ - struct TALER_MERCHANT_TipMerchantGetHandle *tgh; - CURL *eh; - - GNUNET_assert (NULL != backend_url); - tgh = GNUNET_new (struct TALER_MERCHANT_TipMerchantGetHandle); - tgh->ctx = ctx; - tgh->cb = cb; - tgh->cb_cls = cb_cls; - - { - char res_str[sizeof (*tip_id) * 2]; - char arg_str[sizeof (res_str) + 48]; - char *end; - - end = GNUNET_STRINGS_data_to_string (tip_id, - sizeof (*tip_id), - res_str, - sizeof (res_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "private/tips/%s", - res_str); - tgh->url = TALER_url_join (backend_url, - arg_str, - "pickups", pickups ? "yes" : NULL, - NULL); - } - if (NULL == tgh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (tgh); - return NULL; - } - - eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); - tgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_merchant_tip_get_finished, - tgh); - return tgh; -} - - -void -TALER_MERCHANT_merchant_tip_get_cancel (struct - TALER_MERCHANT_TipMerchantGetHandle *tgh) -{ - if (NULL != tgh->job) - { - GNUNET_CURL_job_cancel (tgh->job); - tgh->job = NULL; - } - GNUNET_free (tgh->url); - GNUNET_free (tgh); -} - - -/* end of merchant_api_merchant_get_tip.c */ diff --git a/src/lib/merchant_api_patch_account.c b/src/lib/merchant_api_patch_account.c new file mode 100644 index 00000000..ce0e74d4 --- /dev/null +++ b/src/lib/merchant_api_patch_account.c @@ -0,0 +1,254 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_patch_account.c + * @brief Implementation of the PATCH /accounts/$ID request + * of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_common.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a PATCH /accounts/$ID operation. + */ +struct TALER_MERCHANT_AccountPatchHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_AccountPatchCallback 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 + * HTTP PATCH /accounts/$ID request. + * + * @param cls the `struct TALER_MERCHANT_AccountPatchHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_patch_account_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_AccountPatchHandle *tph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "PATCH /accounts/$ID completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_break_op (0); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + tph->cb (tph->cb_cls, + &hr); + TALER_MERCHANT_account_patch_cancel (tph); +} + + +struct TALER_MERCHANT_AccountPatchHandle * +TALER_MERCHANT_account_patch ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct TALER_MerchantWireHashP *h_wire, + const char *credit_facade_url, + const json_t *credit_facade_credentials, + TALER_MERCHANT_AccountPatchCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_AccountPatchHandle *tph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("credit_facade_url", + credit_facade_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("credit_facade_credentials", + (json_t *) credit_facade_credentials))); + tph = GNUNET_new (struct TALER_MERCHANT_AccountPatchHandle); + tph->ctx = ctx; + tph->cb = cb; + tph->cb_cls = cb_cls; + { + char w_str[sizeof (*h_wire) * 2]; + char *path; + char *end; + + end = GNUNET_STRINGS_data_to_string (h_wire, + sizeof (*h_wire), + w_str, + sizeof (w_str)); + *end = '\0'; + GNUNET_asprintf (&path, + "private/accounts/%s", + w_str); + tph->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (tph->url); + if (GNUNET_OK != + TALER_curl_easy_post (&tph->post_ctx, + eh, + req_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + json_decref (req_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_PATCH)); + tph->job = GNUNET_CURL_job_add2 (ctx, + eh, + tph->post_ctx.headers, + &handle_patch_account_finished, + tph); + } + return tph; +} + + +void +TALER_MERCHANT_account_patch_cancel ( + struct TALER_MERCHANT_AccountPatchHandle *tph) +{ + if (NULL != tph->job) + { + GNUNET_CURL_job_cancel (tph->job); + tph->job = NULL; + } + TALER_curl_easy_post_finished (&tph->post_ctx); + GNUNET_free (tph->url); + GNUNET_free (tph); +} + + +/* end of merchant_api_patch_account.c */ diff --git a/src/lib/merchant_api_patch_instance.c b/src/lib/merchant_api_patch_instance.c index 7a6c390a..f4f39642 100644 --- a/src/lib/merchant_api_patch_instance.c +++ b/src/lib/merchant_api_patch_instance.c @@ -29,7 +29,9 @@ #include <gnunet/gnunet_util_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> +#include <taler/taler_kyclogic_lib.h> #include <taler/taler_curl_lib.h> @@ -156,55 +158,27 @@ TALER_MERCHANT_instance_patch ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const char *instance_id, - unsigned int accounts_length, - const char *payto_uris[], const char *name, const json_t *address, const json_t *jurisdiction, - const struct TALER_Amount *default_max_wire_fee, - uint32_t default_wire_fee_amortization, - const struct TALER_Amount *default_max_deposit_fee, + bool use_stefan, struct GNUNET_TIME_Relative default_wire_transfer_delay, struct GNUNET_TIME_Relative default_pay_delay, TALER_MERCHANT_InstancePatchCallback cb, void *cb_cls) { struct TALER_MERCHANT_InstancePatchHandle *iph; - json_t *jpayto_uris; json_t *req_obj; - jpayto_uris = json_array (); - if (NULL == jpayto_uris) - { - GNUNET_break (0); - return NULL; - } - for (unsigned int i = 0; i<accounts_length; i++) - { - if (0 != - json_array_append_new (jpayto_uris, - json_string (payto_uris[i]))) - { - GNUNET_break (0); - json_decref (jpayto_uris); - return NULL; - } - } req_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_steal ("payto_uris", - jpayto_uris), GNUNET_JSON_pack_string ("name", name), GNUNET_JSON_pack_object_incref ("address", (json_t *) address), GNUNET_JSON_pack_object_incref ("jurisdiction", (json_t *) jurisdiction), - TALER_JSON_pack_amount ("default_max_wire_fee", - default_max_wire_fee), - GNUNET_JSON_pack_uint64 ("default_wire_fee_amortization", - default_wire_fee_amortization), - TALER_JSON_pack_amount ("default_max_deposit_fee", - default_max_deposit_fee), + GNUNET_JSON_pack_bool ("use_stefan", + use_stefan), GNUNET_JSON_pack_time_rel ("default_wire_transfer_delay", default_wire_transfer_delay), GNUNET_JSON_pack_time_rel ("default_pay_delay", diff --git a/src/lib/merchant_api_patch_order_forget.c b/src/lib/merchant_api_patch_order_forget.c index 565a66f3..118655db 100644 --- a/src/lib/merchant_api_patch_order_forget.c +++ b/src/lib/merchant_api_patch_order_forget.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020, 2021 Taler Systems SA + Copyright (C) 2020, 2021, 2023 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 @@ -28,6 +28,7 @@ #include <microhttpd.h> /* just for HTTP status codes */ #include <gnunet/gnunet_util_lib.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> #include <taler/taler_curl_lib.h> @@ -149,13 +150,14 @@ handle_forget_finished (void *cls, struct TALER_MERCHANT_OrderForgetHandle * -TALER_MERCHANT_order_forget (struct GNUNET_CURL_Context *ctx, - const char *merchant_url, - const char *order_id, - unsigned int fields_length, - const char *fields[], - TALER_MERCHANT_ForgetCallback cb, - void *cb_cls) +TALER_MERCHANT_order_forget ( + struct GNUNET_CURL_Context *ctx, + const char *merchant_url, + const char *order_id, + unsigned int fields_length, + const char *fields[static fields_length], + TALER_MERCHANT_ForgetCallback cb, + void *cb_cls) { struct TALER_MERCHANT_OrderForgetHandle *ofh; json_t *req_fields; @@ -235,8 +237,8 @@ TALER_MERCHANT_order_forget (struct GNUNET_CURL_Context *ctx, void -TALER_MERCHANT_order_forget_cancel (struct - TALER_MERCHANT_OrderForgetHandle *ofh) +TALER_MERCHANT_order_forget_cancel ( + struct TALER_MERCHANT_OrderForgetHandle *ofh) { if (NULL != ofh->job) { diff --git a/src/lib/merchant_api_patch_otp_device.c b/src/lib/merchant_api_patch_otp_device.c new file mode 100644 index 00000000..322efe7b --- /dev/null +++ b/src/lib/merchant_api_patch_otp_device.c @@ -0,0 +1,252 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_patch_otp_device.c + * @brief Implementation of the PATCH /otp-devices/$ID request + * of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_common.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a PATCH /otp-devices/$ID operation. + */ +struct TALER_MERCHANT_OtpDevicePatchHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_OtpDevicePatchCallback 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 + * HTTP PATCH /otp-devices/$ID request. + * + * @param cls the `struct TALER_MERCHANT_OtpDevicePatchHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_patch_otp_device_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_OtpDevicePatchHandle *tph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "PATCH /otp-devices/$ID completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_break_op (0); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + tph->cb (tph->cb_cls, + &hr); + TALER_MERCHANT_otp_device_patch_cancel (tph); +} + + +struct TALER_MERCHANT_OtpDevicePatchHandle * +TALER_MERCHANT_otp_device_patch ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *otp_device_id, + const char *otp_device_description, + const char *otp_key, + enum TALER_MerchantConfirmationAlgorithm mca, + uint64_t otp_ctr, + TALER_MERCHANT_OtpDevicePatchCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_OtpDevicePatchHandle *tph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("otp_device_description", + otp_device_description), + GNUNET_JSON_pack_uint64 ("otp_algorithm", + (uint32_t) mca), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("otp_key", + otp_key)), + GNUNET_JSON_pack_uint64 ("otp_ctr", + otp_ctr)); + tph = GNUNET_new (struct TALER_MERCHANT_OtpDevicePatchHandle); + tph->ctx = ctx; + tph->cb = cb; + tph->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/otp-devices/%s", + otp_device_id); + tph->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (tph->url); + if (GNUNET_OK != + TALER_curl_easy_post (&tph->post_ctx, + eh, + req_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + json_decref (req_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_PATCH)); + tph->job = GNUNET_CURL_job_add2 (ctx, + eh, + tph->post_ctx.headers, + &handle_patch_otp_device_finished, + tph); + } + return tph; +} + + +void +TALER_MERCHANT_otp_device_patch_cancel ( + struct TALER_MERCHANT_OtpDevicePatchHandle *tph) +{ + if (NULL != tph->job) + { + GNUNET_CURL_job_cancel (tph->job); + tph->job = NULL; + } + TALER_curl_easy_post_finished (&tph->post_ctx); + GNUNET_free (tph->url); + GNUNET_free (tph); +} + + +/* end of merchant_api_patch_otp_device.c */ diff --git a/src/lib/merchant_api_patch_product.c b/src/lib/merchant_api_patch_product.c index 7f931655..48db078f 100644 --- a/src/lib/merchant_api_patch_product.c +++ b/src/lib/merchant_api_patch_product.c @@ -28,6 +28,7 @@ #include <microhttpd.h> /* just for HTTP status codes */ #include <gnunet/gnunet_util_lib.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> #include <taler/taler_curl_lib.h> diff --git a/src/lib/merchant_api_patch_template.c b/src/lib/merchant_api_patch_template.c new file mode 100644 index 00000000..7dfebf9c --- /dev/null +++ b/src/lib/merchant_api_patch_template.c @@ -0,0 +1,249 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_patch_template.c + * @brief Implementation of the PATCH /templates/$ID request + * of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_common.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a PATCH /templates/$ID operation. + */ +struct TALER_MERCHANT_TemplatePatchHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TemplatePatchCallback 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 + * HTTP PATCH /templates/$ID request. + * + * @param cls the `struct TALER_MERCHANT_TemplatePatchHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_patch_template_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TemplatePatchHandle *tph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "PATCH /templates/$ID completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_break_op (0); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + tph->cb (tph->cb_cls, + &hr); + TALER_MERCHANT_template_patch_cancel (tph); +} + + +struct TALER_MERCHANT_TemplatePatchHandle * +TALER_MERCHANT_template_patch ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *template_id, + const char *template_description, + const char *otp_id, + json_t *template_contract, + TALER_MERCHANT_TemplatePatchCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TemplatePatchHandle *tph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("template_description", + template_description), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("otp_id", + otp_id)), + GNUNET_JSON_pack_object_incref ("template_contract", + (json_t *) template_contract)); + tph = GNUNET_new (struct TALER_MERCHANT_TemplatePatchHandle); + tph->ctx = ctx; + tph->cb = cb; + tph->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/templates/%s", + template_id); + tph->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (tph->url); + if (GNUNET_OK != + TALER_curl_easy_post (&tph->post_ctx, + eh, + req_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + json_decref (req_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_PATCH)); + tph->job = GNUNET_CURL_job_add2 (ctx, + eh, + tph->post_ctx.headers, + &handle_patch_template_finished, + tph); + } + return tph; +} + + +void +TALER_MERCHANT_template_patch_cancel ( + struct TALER_MERCHANT_TemplatePatchHandle *tph) +{ + if (NULL != tph->job) + { + GNUNET_CURL_job_cancel (tph->job); + tph->job = NULL; + } + TALER_curl_easy_post_finished (&tph->post_ctx); + GNUNET_free (tph->url); + GNUNET_free (tph); +} + + +/* end of merchant_api_patch_template.c */ diff --git a/src/lib/merchant_api_patch_webhook.c b/src/lib/merchant_api_patch_webhook.c new file mode 100644 index 00000000..6d8b6340 --- /dev/null +++ b/src/lib/merchant_api_patch_webhook.c @@ -0,0 +1,254 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_patch_webhook.c + * @brief Implementation of the PATCH /webhooks/$ID request + * of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a PATCH /webhooks/$ID operation. + */ +struct TALER_MERCHANT_WebhookPatchHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_WebhookPatchCallback 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 + * HTTP PATCH /webhooks/$ID request. + * + * @param cls the `struct TALER_MERCHANT_WebhookPatchHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_patch_webhook_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_WebhookPatchHandle *wph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + wph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "PATCH /webhooks/$ID completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_break_op (0); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + wph->cb (wph->cb_cls, + &hr); + TALER_MERCHANT_webhook_patch_cancel (wph); +} + + +struct TALER_MERCHANT_WebhookPatchHandle * +TALER_MERCHANT_webhook_patch ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *webhook_id, + const char *event_type, + const char *url, + const char *http_method, + const char *header_template, + const char *body_template, + TALER_MERCHANT_WebhookPatchCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_WebhookPatchHandle *wph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("event_type", + event_type), + GNUNET_JSON_pack_string ("url", + url), + GNUNET_JSON_pack_string ("http_method", + http_method), + GNUNET_JSON_pack_string ("header_template", + header_template), + GNUNET_JSON_pack_string ("body_template", + body_template)); + wph = GNUNET_new (struct TALER_MERCHANT_WebhookPatchHandle); + wph->ctx = ctx; + wph->cb = cb; + wph->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "private/webhooks/%s", + webhook_id); + wph->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (wph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (wph->url); + if (GNUNET_OK != + TALER_curl_easy_post (&wph->post_ctx, + eh, + req_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (req_obj); + GNUNET_free (wph); + return NULL; + } + json_decref (req_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_PATCH)); + wph->job = GNUNET_CURL_job_add2 (ctx, + eh, + wph->post_ctx.headers, + &handle_patch_webhook_finished, + wph); + } + return wph; +} + + +void +TALER_MERCHANT_webhook_patch_cancel ( + struct TALER_MERCHANT_WebhookPatchHandle *wph) +{ + if (NULL != wph->job) + { + GNUNET_CURL_job_cancel (wph->job); + wph->job = NULL; + } + TALER_curl_easy_post_finished (&wph->post_ctx); + GNUNET_free (wph->url); + GNUNET_free (wph); +} + + +/* end of merchant_api_patch_webhook.c */ diff --git a/src/lib/merchant_api_post_account.c b/src/lib/merchant_api_post_account.c new file mode 100644 index 00000000..690aef17 --- /dev/null +++ b/src/lib/merchant_api_post_account.c @@ -0,0 +1,250 @@ +/* + 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 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_post_account.c + * @brief Implementation of the POST /account request + * of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a POST /private/accounts operation. + */ +struct TALER_MERCHANT_AccountsPostHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_AccountsPostCallback 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 + * HTTP POST /account request. + * + * @param cls the `struct TALER_MERCHANT_AccountPostHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_account_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_AccountsPostHandle *aph = cls; + const json_t *json = response; + struct TALER_MERCHANT_AccountsPostResponse apr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + aph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "POST /accounts completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_wire", + &apr.details.ok.h_wire), + GNUNET_JSON_spec_fixed_auto ("salt", + &apr.details.ok.salt), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + apr.hr.http_status = 0; + apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + GNUNET_break_op (0); + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_CONFLICT: + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &apr.hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) apr.hr.ec); + GNUNET_break_op (0); + break; + } + aph->cb (aph->cb_cls, + &apr); + TALER_MERCHANT_accounts_post_cancel (aph); +} + + +struct TALER_MERCHANT_AccountsPostHandle * +TALER_MERCHANT_accounts_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *payto_uri, + const char *credit_facade_url, + const json_t *credit_facade_credentials, + TALER_MERCHANT_AccountsPostCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_AccountsPostHandle *aph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "payto_uri", + payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + "credit_facade_url", + credit_facade_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ( + "credit_facade_credentials", + (json_t *) credit_facade_credentials)) + ); + aph = GNUNET_new (struct TALER_MERCHANT_AccountsPostHandle); + aph->ctx = ctx; + aph->cb = cb; + aph->cb_cls = cb_cls; + aph->url = TALER_url_join (backend_url, + "private/accounts", + NULL); + if (NULL == aph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (aph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (aph->url); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&aph->post_ctx, + eh, + req_obj)); + json_decref (req_obj); + aph->job = GNUNET_CURL_job_add2 (ctx, + eh, + aph->post_ctx.headers, + &handle_post_account_finished, + aph); + GNUNET_assert (NULL != aph->job); + } + return aph; +} + + +void +TALER_MERCHANT_accounts_post_cancel ( + struct TALER_MERCHANT_AccountsPostHandle *aph) +{ + if (NULL != aph->job) + { + GNUNET_CURL_job_cancel (aph->job); + aph->job = NULL; + } + TALER_curl_easy_post_finished (&aph->post_ctx); + GNUNET_free (aph->url); + GNUNET_free (aph); +} + + +/* end of merchant_api_post_account.c */ diff --git a/src/lib/merchant_api_post_instances.c b/src/lib/merchant_api_post_instances.c index 89b7d2fb..c8e5add6 100644 --- a/src/lib/merchant_api_post_instances.c +++ b/src/lib/merchant_api_post_instances.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -29,8 +29,10 @@ #include <gnunet/gnunet_util_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_curl_lib.h> +#include <taler/taler_kyclogic_lib.h> /** @@ -162,14 +164,10 @@ TALER_MERCHANT_instances_post ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const char *instance_id, - unsigned int accounts_length, - const char *payto_uris[], const char *name, const json_t *address, const json_t *jurisdiction, - const struct TALER_Amount *default_max_wire_fee, - uint32_t default_wire_fee_amortization, - const struct TALER_Amount *default_max_deposit_fee, + bool use_stefan, struct GNUNET_TIME_Relative default_wire_transfer_delay, struct GNUNET_TIME_Relative default_pay_delay, const char *auth_token, @@ -177,7 +175,6 @@ TALER_MERCHANT_instances_post ( void *cb_cls) { struct TALER_MERCHANT_InstancesPostHandle *iph; - json_t *jpayto_uris; json_t *req_obj; json_t *auth_obj; @@ -209,28 +206,7 @@ TALER_MERCHANT_instances_post ( GNUNET_break (0); return NULL; } - jpayto_uris = json_array (); - if (NULL == jpayto_uris) - { - json_decref (auth_obj); - GNUNET_break (0); - return NULL; - } - for (unsigned int i = 0; i<accounts_length; i++) - { - if (0 != - json_array_append_new (jpayto_uris, - json_string (payto_uris[i]))) - { - GNUNET_break (0); - json_decref (auth_obj); - json_decref (jpayto_uris); - return NULL; - } - } req_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_steal ("payto_uris", - jpayto_uris), GNUNET_JSON_pack_string ("id", instance_id), GNUNET_JSON_pack_string ("name", @@ -239,12 +215,8 @@ TALER_MERCHANT_instances_post ( (json_t *) address), GNUNET_JSON_pack_object_incref ("jurisdiction", (json_t *) jurisdiction), - TALER_JSON_pack_amount ("default_max_wire_fee", - default_max_wire_fee), - GNUNET_JSON_pack_uint64 ("default_wire_fee_amortization", - default_wire_fee_amortization), - TALER_JSON_pack_amount ("default_max_deposit_fee", - default_max_deposit_fee), + GNUNET_JSON_pack_bool ("use_stefan", + use_stefan), GNUNET_JSON_pack_time_rel ("default_wire_transfer_delay", default_wire_transfer_delay), GNUNET_JSON_pack_time_rel ("default_pay_delay", diff --git a/src/lib/merchant_api_post_order_abort.c b/src/lib/merchant_api_post_order_abort.c index 406a6015..270ceb7e 100644 --- a/src/lib/merchant_api_post_order_abort.c +++ b/src/lib/merchant_api_post_order_abort.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -31,6 +31,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> #include <taler/taler_exchange_service.h> @@ -38,6 +39,12 @@ /** + * Maximum number of refunds we return. + */ +#define MAX_REFUNDS 1024 + + +/** * @brief An abort Handle */ struct TALER_MERCHANT_OrderAbortHandle @@ -101,17 +108,20 @@ struct TALER_MERCHANT_OrderAbortHandle * OK. Otherwise returns #GNUNET_SYSERR. * * @param oah handle to operation that created the reply + * @param[in] ar abort response, partially initialized * @param json the reply to parse * @return #GNUNET_OK on success */ -static int +static enum GNUNET_GenericReturnValue check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah, + struct TALER_MERCHANT_AbortResponse *ar, const json_t *json) { - json_t *refunds; + const json_t *refunds; unsigned int num_refunds; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("refunds", &refunds), + GNUNET_JSON_spec_array_const ("refunds", + &refunds), GNUNET_JSON_spec_end () }; @@ -123,13 +133,14 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah, GNUNET_break_op (0); return GNUNET_SYSERR; } - if (! json_is_array (refunds)) + num_refunds = (unsigned int) json_array_size (refunds); + if ( (json_array_size (refunds) != (size_t) num_refunds) || + (num_refunds > MAX_REFUNDS) ) { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); + GNUNET_break (0); return GNUNET_SYSERR; } - num_refunds = json_array_size (refunds); + { struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)]; @@ -149,7 +160,6 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah, NULL, NULL)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } if (MHD_HTTP_OK == exchange_status) @@ -168,7 +178,6 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah, NULL, NULL)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } @@ -183,26 +192,17 @@ check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah, &res[i].exchange_sig)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); return GNUNET_SYSERR; } } } - { - struct TALER_MERCHANT_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; - - oah->abort_cb (oah->abort_cb_cls, - &hr, - &oah->merchant_pub, - num_refunds, - res); - } + ar->details.ok.merchant_pub = &oah->merchant_pub; + ar->details.ok.num_aborts = num_refunds; + ar->details.ok.aborts = res; + oah->abort_cb (oah->abort_cb_cls, + ar); oah->abort_cb = NULL; } - GNUNET_JSON_parse_free (spec); return GNUNET_OK; } @@ -222,9 +222,9 @@ handle_abort_finished (void *cls, { struct TALER_MERCHANT_OrderAbortHandle *oah = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_AbortResponse ar = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; oah->job = NULL; @@ -234,40 +234,41 @@ handle_abort_finished (void *cls, switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (GNUNET_OK == check_abort_refund (oah, + &ar, json)) { TALER_MERCHANT_order_abort_cancel (oah); return; } - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + ar.hr.http_status = 0; + ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_BAD_REQUEST: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ar.hr.ec = TALER_JSON_get_error_code (json); + ar.hr.hint = TALER_JSON_get_error_hint (json); /* This should never happen, either us or the merchant is buggy (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ar.hr.ec = TALER_JSON_get_error_code (json); + ar.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ar.hr.ec = TALER_JSON_get_error_code (json); + ar.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ break; case MHD_HTTP_REQUEST_TIMEOUT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ar.hr.ec = TALER_JSON_get_error_code (json); + ar.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says one of the signatures is invalid; as we checked them, this should never happen, we should pass the JSON @@ -275,19 +276,19 @@ handle_abort_finished (void *cls, break; case MHD_HTTP_PRECONDITION_FAILED: /* Our *payment* already succeeded fully. */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ar.hr.ec = TALER_JSON_get_error_code (json); + ar.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ar.hr.ec = TALER_JSON_get_error_code (json); + ar.hr.hint = TALER_JSON_get_error_hint (json); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; case MHD_HTTP_BAD_GATEWAY: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &ar.hr); /* Nothing really to verify, the merchant is blaming the exchange. We should pass the JSON reply to the application */ break; @@ -295,33 +296,31 @@ handle_abort_finished (void *cls, /* unexpected response code */ TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &ar.hr); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) ar.hr.ec); GNUNET_break_op (0); break; } oah->abort_cb (oah->abort_cb_cls, - &hr, - NULL, - 0, - NULL); + &ar); TALER_MERCHANT_order_abort_cancel (oah); } struct TALER_MERCHANT_OrderAbortHandle * -TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx, - const char *merchant_url, - const char *order_id, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_PrivateContractHashP *h_contract, - unsigned int num_coins, - const struct TALER_MERCHANT_AbortCoin coins[], - TALER_MERCHANT_AbortCallback cb, - void *cb_cls) +TALER_MERCHANT_order_abort ( + struct GNUNET_CURL_Context *ctx, + const char *merchant_url, + const char *order_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_PrivateContractHashP *h_contract, + unsigned int num_coins, + const struct TALER_MERCHANT_AbortCoin coins[static num_coins], + TALER_MERCHANT_AbortCallback cb, + void *cb_cls) { struct TALER_MERCHANT_OrderAbortHandle *oah; json_t *abort_obj; @@ -388,9 +387,9 @@ TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx, oah->num_coins = num_coins; oah->coins = GNUNET_new_array (num_coins, struct TALER_MERCHANT_AbortCoin); - memcpy (oah->coins, - coins, - num_coins * sizeof (struct TALER_MERCHANT_AbortCoin)); + GNUNET_memcpy (oah->coins, + coins, + num_coins * sizeof (struct TALER_MERCHANT_AbortCoin)); { CURL *eh; diff --git a/src/lib/merchant_api_post_order_claim.c b/src/lib/merchant_api_post_order_claim.c index 0a70c1b0..76802ea5 100644 --- a/src/lib/merchant_api_post_order_claim.c +++ b/src/lib/merchant_api_post_order_claim.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -86,36 +86,36 @@ handle_post_order_claim_finished (void *cls, const void *response) { struct TALER_MERCHANT_OrderClaimHandle *och = cls; - json_t *contract_terms; - struct TALER_MerchantSignatureP sig; - struct TALER_PrivateContractHashP hash; const json_t *json = response; + struct TALER_MERCHANT_OrderClaimResponse ocr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("contract_terms", - &contract_terms), - GNUNET_JSON_spec_fixed_auto ("sig", - &sig), + GNUNET_JSON_spec_object_const ( + "contract_terms", + &ocr.details.ok.contract_terms), + GNUNET_JSON_spec_fixed_auto ( + "sig", + &ocr.details.ok.sig), GNUNET_JSON_spec_end () }; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; och->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Order claimed with status %u\n", + (unsigned int) response_code); + if (MHD_HTTP_OK != response_code) { - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ocr.hr.ec = TALER_JSON_get_error_code (json); + ocr.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); + (int) ocr.hr.ec); och->cb (och->cb_cls, - &hr, - NULL, - NULL, - NULL); + &ocr); TALER_MERCHANT_order_claim_cancel (och); return; } @@ -128,39 +128,29 @@ handle_post_order_claim_finished (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Claiming order failed: could not parse JSON response\n"); GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.http_status = 0; + ocr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + ocr.hr.http_status = 0; och->cb (och->cb_cls, - &hr, - NULL, - NULL, - NULL); + &ocr); TALER_MERCHANT_order_claim_cancel (och); return; } if (GNUNET_OK != - TALER_JSON_contract_hash (contract_terms, - &hash)) + TALER_JSON_contract_hash (ocr.details.ok.contract_terms, + &ocr.details.ok.h_contract_terms)) { GNUNET_break (0); - hr.ec = TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_CLIENT_INTERNAL_FAILURE; - hr.http_status = 0; + ocr.hr.ec = TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_CLIENT_INTERNAL_FAILURE; + ocr.hr.http_status = 0; GNUNET_JSON_parse_free (spec); och->cb (och->cb_cls, - &hr, - NULL, - NULL, - NULL); + &ocr); TALER_MERCHANT_order_claim_cancel (och); return; } - och->cb (och->cb_cls, - &hr, - contract_terms, - &sig, - &hash); + &ocr); GNUNET_JSON_parse_free (spec); TALER_MERCHANT_order_claim_cancel (och); } diff --git a/src/lib/merchant_api_post_order_paid.c b/src/lib/merchant_api_post_order_paid.c index 2c398f90..785d956f 100644 --- a/src/lib/merchant_api_post_order_paid.c +++ b/src/lib/merchant_api_post_order_paid.c @@ -30,6 +30,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> #include <taler/taler_exchange_service.h> @@ -89,9 +90,9 @@ handle_paid_finished (void *cls, { struct TALER_MERCHANT_OrderPaidHandle *oph = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_OrderPaidResponse opr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; oph->job = NULL; @@ -101,61 +102,81 @@ handle_paid_finished (void *cls, switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + opr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; - case MHD_HTTP_NO_CONTENT: + case MHD_HTTP_OK: + { + bool refunded; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_bool ("refunded", + &refunded), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (opr.hr.reply, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + opr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + break; + } break; case MHD_HTTP_BAD_REQUEST: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + opr.hr.ec = TALER_JSON_get_error_code (json); + opr.hr.hint = TALER_JSON_get_error_hint (json); /* This should never happen, either us * or the merchant is buggy (or API version conflict); * just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + opr.hr.ec = TALER_JSON_get_error_code (json); + opr.hr.hint = TALER_JSON_get_error_hint (json); /* The signature provided was invalid */ break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + opr.hr.ec = TALER_JSON_get_error_code (json); + opr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ break; case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + opr.hr.ec = TALER_JSON_get_error_code (json); + opr.hr.hint = TALER_JSON_get_error_hint (json); /* The hashed contract terms don't match with the order_id. */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + opr.hr.ec = TALER_JSON_get_error_code (json); + opr.hr.hint = TALER_JSON_get_error_hint (json); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; case MHD_HTTP_SERVICE_UNAVAILABLE: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &opr.hr); /* Exchange couldn't respond properly; the retry is left to the application */ break; default: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &opr.hr); /* unexpected response code */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) opr.hr.ec); GNUNET_break_op (0); break; } oph->paid_cb (oph->paid_cb_cls, - &hr); + &opr); TALER_MERCHANT_order_paid_cancel (oph); } @@ -167,6 +188,7 @@ TALER_MERCHANT_order_paid ( const char *order_id, const char *session_id, const struct TALER_PrivateContractHashP *h_contract_terms, + const struct GNUNET_HashCode *wallet_data_hash, const struct TALER_MerchantSignatureP *merchant_sig, TALER_MERCHANT_OrderPaidCallback paid_cb, void *paid_cb_cls) @@ -179,6 +201,9 @@ TALER_MERCHANT_order_paid ( merchant_sig), GNUNET_JSON_pack_data_auto ("h_contract", h_contract_terms), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("wallet_data_hash", + wallet_data_hash)), GNUNET_JSON_pack_string ("session_id", session_id)); oph = GNUNET_new (struct TALER_MERCHANT_OrderPaidHandle); diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c index c246a1d4..57c85565 100644 --- a/src/lib/merchant_api_post_order_pay.c +++ b/src/lib/merchant_api_post_order_pay.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -30,6 +30,7 @@ #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> @@ -91,6 +92,23 @@ struct TALER_MERCHANT_OrderPayHandle struct TALER_MerchantPublicKeyP merchant_pub; /** + * JSON with the full reply, used during async + * processing. + */ + json_t *full_reply; + + /** + * Pointer into @e coins array for the coin that + * created a conflict (that we are checking). + */ + const struct TALER_MERCHANT_PaidCoin *error_pc; + + /** + * Coin history that proves a conflict. + */ + json_t *error_history; + + /** * Number of @e coins we are paying with. */ unsigned int num_coins; @@ -105,132 +123,6 @@ struct TALER_MERCHANT_OrderPayHandle /** - * We got a 409 response back from the exchange (or the merchant). - * Now we need to check the provided cryptograophic proof that the - * coin was actually already spent! - * - * @param pc handle of the original coin we paid with - * @param json cryptograophic proof of coin's transaction - * history as was returned by the exchange/merchant - * @return #GNUNET_OK if proof checks out - */ -static int -check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc, - json_t *json) -{ - struct TALER_Amount spent; - struct TALER_Amount spent_plus_contrib; - struct TALER_DenominationHashP h_denom_pub; - struct TALER_DenominationHashP h_denom_pub_pc; - - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */ - pc->amount_with_fee.currency, - &pc->coin_pub, - json, - &h_denom_pub, - &spent)) - { - /* Exchange's history fails to verify */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&spent_plus_contrib, - &spent, - &pc->amount_with_fee)) - { - /* We got an integer overflow? Bad application! */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - TALER_denom_pub_hash (&pc->denom_pub, - &h_denom_pub_pc); - if ( (-1 != TALER_amount_cmp (&pc->denom_value, - &spent_plus_contrib)) && - (0 != GNUNET_memcmp (&h_denom_pub, - &h_denom_pub_pc)) ) - { - /* according to our calculations, the transaction should - have still worked, AND we did not get any proof of - coin public key re-use; hence: exchange error! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Accepting proof of double-spending (or coin public key re-use)\n"); - return GNUNET_OK; -} - - -/** - * We got a 409 response back from the exchange (or the merchant). - * Now we need to check the provided cryptograophic proof that the - * coin was actually already spent! - * - * @param oph handle of the original pay operation - * @param json cryptograophic proof returned by the - * exchange/merchant - * @return #GNUNET_OK if proof checks out - */ -static enum GNUNET_GenericReturnValue -check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph, - const json_t *json) -{ - json_t *history; - json_t *ereply; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("exchange_reply", &ereply), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), - GNUNET_JSON_spec_end () - }; - struct GNUNET_JSON_Specification hspec[] = { - GNUNET_JSON_spec_json ("history", &history), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_JSON_parse (ereply, - hspec, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (spec); - - for (unsigned int i = 0; i<oph->num_coins; i++) - { - if (0 == memcmp (&oph->coins[i].coin_pub, - &coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP))) - { - int ret; - - ret = check_coin_history (&oph->coins[i], - history); - GNUNET_JSON_parse_free (hspec); - return ret; - } - } - GNUNET_break_op (0); /* complaint is not about any of the coins - that we actually paid with... */ - GNUNET_JSON_parse_free (hspec); - return GNUNET_SYSERR; -} - - -/** * Function called when we're done processing the * HTTP /pay request. * @@ -245,12 +137,10 @@ handle_pay_finished (void *cls, { struct TALER_MERCHANT_OrderPayHandle *oph = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_PayResponse pr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; - struct TALER_MerchantSignatureP *merchant_sigp = NULL; - struct TALER_MerchantSignatureP merchant_sig; oph->job = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -259,15 +149,21 @@ handle_pay_finished (void *cls, switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (oph->am_wallet) { /* Here we can (and should) verify the merchant's signature */ struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("sig", - &merchant_sig), + GNUNET_JSON_spec_fixed_auto ( + "sig", + &pr.details.ok.merchant_sig), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ( + "pos_confirmation", + &pr.details.ok.pos_confirmation), + NULL), GNUNET_JSON_spec_end () }; @@ -277,94 +173,90 @@ handle_pay_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.http_status = 0; - hr.hint = "sig field missing in response"; + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "sig field missing in response"; break; } if (GNUNET_OK != TALER_merchant_pay_verify (&oph->h_contract_terms, &oph->merchant_pub, - &merchant_sig)) + &pr.details.ok.merchant_sig)) { GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.http_status = 0; - hr.hint = "signature invalid"; + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "signature invalid"; } - merchant_sigp = &merchant_sig; } break; /* Tolerating Not Acceptable because sometimes * - especially in tests - we might want to POST * coins one at a time. */ case MHD_HTTP_NOT_ACCEPTABLE: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_BAD_REQUEST: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* This should never happen, either us * or the merchant is buggy (or API version conflict); * just pass JSON reply to the application */ break; case MHD_HTTP_PAYMENT_REQUIRED: /* was originally paid, but then refunded */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we tried to abort the payment * after it was successful. We should pass the JSON reply to the * application */ break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ break; - case MHD_HTTP_PRECONDITION_FAILED: - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - /* Nothing really to verify, the merchant is blaming us for failing to - satisfy some constraint (likely it does not like our exchange because - of some disagreement on the PKI). We should pass the JSON reply to the - application */ - break; case MHD_HTTP_REQUEST_TIMEOUT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* The merchant couldn't generate a timely response, likely because it itself waited too long on the exchange. Pass on to application. */ break; case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - if (GNUNET_OK != check_conflict (oph, - json)) - { - GNUNET_break_op (0); - response_code = 0; - } + TALER_MERCHANT_parse_error_details_ (json, + MHD_HTTP_CONFLICT, + &pr.hr); break; case MHD_HTTP_GONE: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); /* The merchant says we are too late, the offer has expired or some denomination key of a coin involved has expired. Might be a disagreement in timestamps? Still, pass on to application. */ break; + case MHD_HTTP_PRECONDITION_FAILED: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* Nothing really to verify, the merchant is blaming us for failing to + satisfy some constraint (likely it does not like our exchange because + of some disagreement on the PKI). We should pass the JSON reply to the + application */ + break; case MHD_HTTP_INTERNAL_SERVER_ERROR: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; @@ -373,37 +265,36 @@ handle_pay_finished (void *cls, We should pass the JSON reply to the application */ TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); break; case MHD_HTTP_SERVICE_UNAVAILABLE: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); /* Exchange couldn't respond properly; the retry is left to the application */ break; case MHD_HTTP_GATEWAY_TIMEOUT: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); /* Exchange couldn't respond in a timely fashion; the retry is left to the application */ break; default: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); /* unexpected response code */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) pr.hr.ec); GNUNET_break_op (0); break; } oph->pay_cb (oph->pay_cb_cls, - &hr, - merchant_sigp); + &pr); TALER_MERCHANT_order_pay_cancel (oph); } @@ -414,8 +305,9 @@ TALER_MERCHANT_order_pay_frontend ( const char *merchant_url, const char *order_id, const char *session_id, + const json_t *wallet_data, unsigned int num_coins, - const struct TALER_MERCHANT_PaidCoin coins[], + const struct TALER_MERCHANT_PaidCoin coins[static num_coins], TALER_MERCHANT_OrderPayCallback pay_cb, void *pay_cb_cls) { @@ -432,6 +324,7 @@ TALER_MERCHANT_order_pay_frontend ( return NULL; } j_coins = json_array (); + GNUNET_assert (NULL != j_coins); for (unsigned int i = 0; i<num_coins; i++) { json_t *j_coin; @@ -503,6 +396,9 @@ TALER_MERCHANT_order_pay_frontend ( GNUNET_JSON_pack_array_steal ("coins", j_coins), GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("wallet_data", + (json_t *) wallet_data)), + GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("session_id", session_id))); @@ -532,9 +428,9 @@ TALER_MERCHANT_order_pay_frontend ( oph->num_coins = num_coins; oph->coins = GNUNET_new_array (num_coins, struct TALER_MERCHANT_PaidCoin); - memcpy (oph->coins, - coins, - num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); + GNUNET_memcpy (oph->coins, + coins, + num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); eh = TALER_MERCHANT_curl_easy_get_ (oph->url); if (GNUNET_OK != @@ -565,6 +461,7 @@ TALER_MERCHANT_order_pay ( const char *merchant_url, const char *session_id, const struct TALER_PrivateContractHashP *h_contract_terms, + const json_t *wallet_data, const struct TALER_Amount *amount, const struct TALER_Amount *max_fee, const struct TALER_MerchantPublicKeyP *merchant_pub, @@ -575,10 +472,12 @@ TALER_MERCHANT_order_pay ( const struct TALER_MerchantWireHashP *h_wire, const char *order_id, unsigned int num_coins, - const struct TALER_MERCHANT_PayCoin coins[], + const struct TALER_MERCHANT_PayCoin coins[static num_coins], TALER_MERCHANT_OrderPayCallback pay_cb, void *pay_cb_cls) { + struct GNUNET_HashCode wallet_data_hash; + if (GNUNET_YES != TALER_amount_cmp_currency (amount, max_fee)) @@ -586,7 +485,9 @@ TALER_MERCHANT_order_pay ( GNUNET_break (0); return NULL; } - + if (NULL != wallet_data) + TALER_json_hash (wallet_data, + &wallet_data_hash); { struct TALER_MERCHANT_PaidCoin pc[num_coins]; @@ -613,6 +514,9 @@ TALER_MERCHANT_order_pay ( &fee, h_wire, h_contract_terms, + (NULL != wallet_data) + ? &wallet_data_hash + : NULL, coin->h_age_commitment, NULL /* h_extensions! */, &h_denom_pub, @@ -637,6 +541,7 @@ TALER_MERCHANT_order_pay ( merchant_url, order_id, session_id, + wallet_data, num_coins, pc, pay_cb, @@ -661,6 +566,8 @@ TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph) oph->job = NULL; } TALER_curl_easy_post_finished (&oph->post_ctx); + json_decref (oph->error_history); + json_decref (oph->full_reply); GNUNET_free (oph->coins); GNUNET_free (oph->url); GNUNET_free (oph); diff --git a/src/lib/merchant_api_post_order_refund.c b/src/lib/merchant_api_post_order_refund.c index c59bd762..4414bd86 100644 --- a/src/lib/merchant_api_post_order_refund.c +++ b/src/lib/merchant_api_post_order_refund.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -28,6 +28,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> #include <taler/taler_curl_lib.h> @@ -84,30 +85,26 @@ handle_refund_finished (void *cls, { struct TALER_MERCHANT_OrderRefundHandle *orh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_RefundResponse rr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; orh->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL); + rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { - const char *taler_refund_uri; - struct TALER_PrivateContractHashP h_contract; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("taler_refund_uri", - &taler_refund_uri), - GNUNET_JSON_spec_fixed_auto ("h_contract", - &h_contract), + GNUNET_JSON_spec_string ( + "taler_refund_uri", + &rr.details.ok.taler_refund_uri), + GNUNET_JSON_spec_fixed_auto ( + "h_contract", + &rr.details.ok.h_contract), GNUNET_JSON_spec_end () }; @@ -117,51 +114,36 @@ handle_refund_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL); + rr.hr.http_status = 0; + rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - orh->cb (orh->cb_cls, - &hr, - taler_refund_uri, - &h_contract); - GNUNET_JSON_parse_free (spec); + break; } - break; case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rr.hr.ec = TALER_JSON_get_error_code (json); + rr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + rr.hr.ec = TALER_JSON_get_error_code (json); + rr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_NOT_FOUND: case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL); + rr.hr.ec = TALER_JSON_get_error_code (json); + rr.hr.hint = TALER_JSON_get_error_hint (json); break; default: GNUNET_break_op (0); /* unexpected status code */ TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL); + &rr.hr); break; } + orh->cb (orh->cb_cls, + &rr); TALER_MERCHANT_post_order_refund_cancel (orh); } diff --git a/src/lib/merchant_api_post_orders.c b/src/lib/merchant_api_post_orders.c index c4ccea36..56881133 100644 --- a/src/lib/merchant_api_post_orders.c +++ b/src/lib/merchant_api_post_orders.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -30,6 +30,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> #include <taler/taler_curl_lib.h> @@ -88,132 +89,12 @@ handle_post_order_finished (void *cls, { struct TALER_MERCHANT_PostOrdersHandle *po = cls; const json_t *json = response; - struct TALER_MERCHANT_PostOrdersReply por = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - struct TALER_ClaimTokenP token; po->job = NULL; - switch (response_code) - { - case 0: - por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - bool no_token; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("order_id", - &por.details.ok.order_id), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("token", - &token), - &no_token), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - por.hr.http_status = 0; - por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - else - { - if (! no_token) - por.details.ok.token = &token; - } - } - break; - case MHD_HTTP_BAD_REQUEST: - json_dumpf (json, - stderr, - JSON_INDENT (2)); - por.hr.ec = TALER_JSON_get_error_code (json); - por.hr.hint = TALER_JSON_get_error_hint (json); - /* This should never happen, either us or - the merchant is buggy (or API version conflict); - just pass JSON reply to the application */ - break; - case MHD_HTTP_UNAUTHORIZED: - por.hr.ec = TALER_JSON_get_error_code (json); - por.hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, merchant says one - of the signatures is invalid; as we checked them, - this should never happen, we should pass the JSON - reply to the application */ - por.hr.ec = TALER_JSON_get_error_code (json); - por.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - por.hr.ec = TALER_JSON_get_error_code (json); - por.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_CONFLICT: - por.hr.ec = TALER_JSON_get_error_code (json); - por.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_GONE: - /* The quantity of some product requested was not available. */ - { - - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ( - "product_id", - &por.details.gone.product_id), - GNUNET_JSON_spec_uint64 ( - "requested_quantity", - &por.details.gone.requested_quantity), - GNUNET_JSON_spec_uint64 ( - "available_quantity", - &por.details.gone.available_quantity), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ( - "restock_expected", - &por.details.gone.restock_expected), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - por.hr.http_status = 0; - por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - } - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, - but this API leaves this to the application */ - por.hr.ec = TALER_JSON_get_error_code (json); - por.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - por.hr.ec = TALER_JSON_get_error_code (json); - por.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) por.hr.ec); - GNUNET_break_op (0); - break; - } - po->cb (po->cb_cls, - &por); + TALER_MERCHANT_handle_order_creation_response_ (po->cb, + po->cb_cls, + response_code, + json); TALER_MERCHANT_orders_post_cancel (po); } @@ -226,6 +107,8 @@ TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx, TALER_MERCHANT_PostOrdersCallback cb, void *cb_cls) { + static const char *no_uuids[GNUNET_NZL (0)]; + return TALER_MERCHANT_orders_post2 (ctx, backend_url, order, @@ -234,7 +117,7 @@ TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx, 0, NULL, 0, - NULL, + no_uuids, true, cb, cb_cls); @@ -251,7 +134,40 @@ TALER_MERCHANT_orders_post2 ( unsigned int inventory_products_length, const struct TALER_MERCHANT_InventoryProduct inventory_products[], unsigned int uuids_length, - const char *uuids[], + const char *uuids[static uuids_length], + bool create_token, + TALER_MERCHANT_PostOrdersCallback cb, + void *cb_cls) +{ + return TALER_MERCHANT_orders_post3 ( + ctx, + backend_url, + order, + NULL, /* session ID */ + refund_delay, + payment_target, + inventory_products_length, + inventory_products, + uuids_length, + uuids, + create_token, + cb, + cb_cls); +} + + +struct TALER_MERCHANT_PostOrdersHandle * +TALER_MERCHANT_orders_post3 ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const json_t *order, + const char *session_id, + struct GNUNET_TIME_Relative refund_delay, + const char *payment_target, + unsigned int inventory_products_length, + const struct TALER_MERCHANT_InventoryProduct inventory_products[], + unsigned int uuids_length, + const char *uuids[static uuids_length], bool create_token, TALER_MERCHANT_PostOrdersCallback cb, void *cb_cls) @@ -271,6 +187,9 @@ TALER_MERCHANT_orders_post2 ( GNUNET_JSON_pack_object_incref ("order", (json_t *) order), GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("session_id", + session_id)), + GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("payment_target", payment_target))); if (0 != refund_delay.rel_value_us) diff --git a/src/lib/merchant_api_post_otp_devices.c b/src/lib/merchant_api_post_otp_devices.c new file mode 100644 index 00000000..456abd09 --- /dev/null +++ b/src/lib/merchant_api_post_otp_devices.c @@ -0,0 +1,237 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_post_otp_devices.c + * @brief Implementation of the POST /otp-devices request + * of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a POST /otp-devices/$ID operation. + */ +struct TALER_MERCHANT_OtpDevicesPostHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_OtpDevicesPostCallback 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 + * HTTP POST /otp-devices request. + * + * @param cls the `struct TALER_MERCHANT_OtpDevicesPostHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_otp_devices_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_OtpDevicesPostHandle *tph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "POST /otp-devices completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + tph->cb (tph->cb_cls, + &hr); + TALER_MERCHANT_otp_devices_post_cancel (tph); +} + + +struct TALER_MERCHANT_OtpDevicesPostHandle * +TALER_MERCHANT_otp_devices_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *otp_device_id, + const char *otp_device_description, + const char *otp_key, + enum TALER_MerchantConfirmationAlgorithm otp_algorithm, + uint64_t otp_ctr, + TALER_MERCHANT_OtpDevicesPostCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_OtpDevicesPostHandle *tph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("otp_device_id", + otp_device_id), + GNUNET_JSON_pack_string ("otp_device_description", + otp_device_description), + GNUNET_JSON_pack_uint64 ("otp_algorithm", + (uint32_t) otp_algorithm), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("otp_key", + otp_key)), + GNUNET_JSON_pack_uint64 ("otp_ctr", + otp_ctr)); + tph = GNUNET_new (struct TALER_MERCHANT_OtpDevicesPostHandle); + tph->ctx = ctx; + tph->cb = cb; + tph->cb_cls = cb_cls; + tph->url = TALER_url_join (backend_url, + "private/otp-devices", + NULL); + if (NULL == tph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (tph->url); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&tph->post_ctx, + eh, + req_obj)); + json_decref (req_obj); + tph->job = GNUNET_CURL_job_add2 (ctx, + eh, + tph->post_ctx.headers, + &handle_post_otp_devices_finished, + tph); + GNUNET_assert (NULL != tph->job); + } + return tph; +} + + +void +TALER_MERCHANT_otp_devices_post_cancel ( + struct TALER_MERCHANT_OtpDevicesPostHandle *tph) +{ + if (NULL != tph->job) + { + GNUNET_CURL_job_cancel (tph->job); + tph->job = NULL; + } + TALER_curl_easy_post_finished (&tph->post_ctx); + GNUNET_free (tph->url); + GNUNET_free (tph); +} + + +/* end of merchant_api_post_otp_devices.c */ diff --git a/src/lib/merchant_api_post_products.c b/src/lib/merchant_api_post_products.c index 43799032..5d0ad27e 100644 --- a/src/lib/merchant_api_post_products.c +++ b/src/lib/merchant_api_post_products.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020-2021 Taler Systems SA + 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 Lesser General Public License as @@ -29,6 +29,7 @@ #include <gnunet/gnunet_util_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_curl_lib.h> @@ -158,7 +159,7 @@ handle_post_products_finished (void *cls, struct TALER_MERCHANT_ProductsPostHandle * -TALER_MERCHANT_products_post ( +TALER_MERCHANT_products_post3 ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const char *product_id, @@ -171,31 +172,57 @@ TALER_MERCHANT_products_post ( int64_t total_stock, const json_t *address, struct GNUNET_TIME_Timestamp next_restock, + uint32_t minimum_age, + unsigned int num_cats, + const uint64_t *cats, TALER_MERCHANT_ProductsPostCallback cb, void *cb_cls) { struct TALER_MERCHANT_ProductsPostHandle *pph; json_t *req_obj; + json_t *categories; + if (0 == num_cats) + { + categories = NULL; + } + else + { + categories = json_array (); + GNUNET_assert (NULL != categories); + for (unsigned int i = 0; i<num_cats; i++) + GNUNET_assert (0 == + json_array_append_new (categories, + json_integer (cats[i]))); + } req_obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("product_id", product_id), GNUNET_JSON_pack_string ("description", description), - GNUNET_JSON_pack_object_incref ("description_i18n", - (json_t *) description_i18n), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + (json_t *) description_i18n)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ("categories", + categories)), GNUNET_JSON_pack_string ("unit", unit), TALER_JSON_pack_amount ("price", price), GNUNET_JSON_pack_string ("image", image), - GNUNET_JSON_pack_array_incref ("taxes", - (json_t *) taxes), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("taxes", + (json_t *) taxes)), GNUNET_JSON_pack_uint64 ("total_stock", total_stock), - GNUNET_JSON_pack_object_incref ("address", - (json_t *) address), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_uint64 ("minimum_age", + minimum_age)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("address", + (json_t *) address)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_timestamp ("next_restock", next_restock))); @@ -234,6 +261,79 @@ TALER_MERCHANT_products_post ( } +struct TALER_MERCHANT_ProductsPostHandle * +TALER_MERCHANT_products_post2 ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *product_id, + const char *description, + const json_t *description_i18n, + const char *unit, + const struct TALER_Amount *price, + const char *image, + const json_t *taxes, + int64_t total_stock, + const json_t *address, + struct GNUNET_TIME_Timestamp next_restock, + uint32_t minimum_age, + TALER_MERCHANT_ProductsPostCallback cb, + void *cb_cls) +{ + return TALER_MERCHANT_products_post3 (ctx, + backend_url, + product_id, + description, + description_i18n, + unit, + price, + image, + taxes, + total_stock, + address, + next_restock, + minimum_age, + 0, + NULL, + cb, + cb_cls); +} + + +struct TALER_MERCHANT_ProductsPostHandle * +TALER_MERCHANT_products_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *product_id, + const char *description, + const json_t *description_i18n, + const char *unit, + const struct TALER_Amount *price, + const char *image, + const json_t *taxes, + int64_t total_stock, + const json_t *address, + struct GNUNET_TIME_Timestamp next_restock, + TALER_MERCHANT_ProductsPostCallback cb, + void *cb_cls) +{ + return TALER_MERCHANT_products_post2 (ctx, + backend_url, + product_id, + description, + description_i18n, + unit, + price, + image, + taxes, + total_stock, + address, + next_restock, + 0, + cb, + cb_cls); +} + + void TALER_MERCHANT_products_post_cancel ( struct TALER_MERCHANT_ProductsPostHandle *pph) diff --git a/src/lib/merchant_api_post_reserves.c b/src/lib/merchant_api_post_reserves.c deleted file mode 100644 index 7942e3e0..00000000 --- a/src/lib/merchant_api_post_reserves.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2016, 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 - <http://www.gnu.org/licenses/> -*/ -/** - * @file merchant_api_post_reserves.c - * @brief Implementation of the POST /reserves request of the merchant's HTTP API - * @author Marcello Stanisci - * @author Christian Grothoff - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_curl_lib.h> -#include <taler/taler_json_lib.h> - - -/** - * @brief A handle for POSTing reserve data. - */ -struct TALER_MERCHANT_PostReservesHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_PostReservesCallback 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 - * HTTP POST /reserves request. - * - * @param cls the `struct TALER_MERCHANT_PostReservesHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_post_reserves_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_PostReservesHandle *prh = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - prh->job = NULL; - switch (response_code) - { - case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - const char *payto_uri; - struct TALER_ReservePublicKeyP reserve_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &reserve_pub), - GNUNET_JSON_spec_string ("payto_uri", - &payto_uri), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - else - { - prh->cb (prh->cb_cls, - &hr, - &reserve_pub, - payto_uri); - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_reserves_post_cancel (prh); - return; - } - } - case MHD_HTTP_ACCEPTED: - break; - case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Did not find any data\n"); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - break; - } - prh->cb (prh->cb_cls, - &hr, - NULL, - NULL); - TALER_MERCHANT_reserves_post_cancel (prh); -} - - -struct TALER_MERCHANT_PostReservesHandle * -TALER_MERCHANT_reserves_post ( - struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_Amount *initial_balance, - const char *exchange_url, - const char *wire_method, - TALER_MERCHANT_PostReservesCallback cb, - void *cb_cls) -{ - struct TALER_MERCHANT_PostReservesHandle *prh; - CURL *eh; - json_t *req; - - prh = GNUNET_new (struct TALER_MERCHANT_PostReservesHandle); - prh->ctx = ctx; - prh->cb = cb; - prh->cb_cls = cb_cls; - prh->url = TALER_url_join (backend_url, - "private/reserves", - NULL); - if (NULL == prh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (prh); - return NULL; - } - req = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("initial_balance", - initial_balance), - GNUNET_JSON_pack_string ("wire_method", - wire_method), - GNUNET_JSON_pack_string ("exchange_url", - exchange_url)); - eh = TALER_MERCHANT_curl_easy_get_ (prh->url); - if (GNUNET_OK != - TALER_curl_easy_post (&prh->post_ctx, - eh, - req)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (req); - GNUNET_free (prh->url); - GNUNET_free (prh); - return NULL; - } - json_decref (req); - prh->job = GNUNET_CURL_job_add2 (ctx, - eh, - prh->post_ctx.headers, - &handle_post_reserves_finished, - prh); - return prh; -} - - -void -TALER_MERCHANT_reserves_post_cancel ( - struct TALER_MERCHANT_PostReservesHandle *prh) -{ - if (NULL != prh->job) - { - GNUNET_CURL_job_cancel (prh->job); - prh->job = NULL; - } - GNUNET_free (prh->url); - TALER_curl_easy_post_finished (&prh->post_ctx); - GNUNET_free (prh); -} - - -/* end of merchant_api_post_reserves.c */ diff --git a/src/lib/merchant_api_post_templates.c b/src/lib/merchant_api_post_templates.c new file mode 100644 index 00000000..3ab4320c --- /dev/null +++ b/src/lib/merchant_api_post_templates.c @@ -0,0 +1,279 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_post_templates.c + * @brief Implementation of the POST /templates request + * of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a POST /templates/$ID operation. + */ +struct TALER_MERCHANT_TemplatesPostHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TemplatesPostCallback 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 + * HTTP POST /templates request. + * + * @param cls the `struct TALER_MERCHANT_TemplatesPostHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_templates_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TemplatesPostHandle *tph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + tph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "POST /templates completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + tph->cb (tph->cb_cls, + &hr); + TALER_MERCHANT_templates_post_cancel (tph); +} + + +static bool +test_template_contract_valid (const json_t *template_contract) +{ + const char *summary; + struct TALER_Amount amount = { .value = 0}; + uint32_t minimum_age = 0; + struct GNUNET_TIME_Relative pay_duration = { 0 }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("summary", + &summary), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("amount", + &amount), + NULL), + GNUNET_JSON_spec_uint32 ("minimum_age", + &minimum_age), + GNUNET_JSON_spec_relative_time ("pay_duration", + &pay_duration), + GNUNET_JSON_spec_end () + }; + const char *ename; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (template_contract, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid template_contract for field %s\n", + ename); + return false; + } + return true; +} + + +struct TALER_MERCHANT_TemplatesPostHandle * +TALER_MERCHANT_templates_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *template_id, + const char *template_description, + const char *otp_id, + const json_t *template_contract, + TALER_MERCHANT_TemplatesPostCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TemplatesPostHandle *tph; + json_t *req_obj; + + if (! test_template_contract_valid (template_contract)) + { + GNUNET_break (0); + return NULL; + } + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("template_id", + template_id), + GNUNET_JSON_pack_string ("template_description", + template_description), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("otp_id", + otp_id)), + GNUNET_JSON_pack_object_incref ("template_contract", + (json_t *) template_contract)); + tph = GNUNET_new (struct TALER_MERCHANT_TemplatesPostHandle); + tph->ctx = ctx; + tph->cb = cb; + tph->cb_cls = cb_cls; + tph->url = TALER_url_join (backend_url, + "private/templates", + NULL); + if (NULL == tph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (tph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (tph->url); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&tph->post_ctx, + eh, + req_obj)); + json_decref (req_obj); + tph->job = GNUNET_CURL_job_add2 (ctx, + eh, + tph->post_ctx.headers, + &handle_post_templates_finished, + tph); + GNUNET_assert (NULL != tph->job); + } + return tph; +} + + +void +TALER_MERCHANT_templates_post_cancel ( + struct TALER_MERCHANT_TemplatesPostHandle *tph) +{ + if (NULL != tph->job) + { + GNUNET_CURL_job_cancel (tph->job); + tph->job = NULL; + } + TALER_curl_easy_post_finished (&tph->post_ctx); + GNUNET_free (tph->url); + GNUNET_free (tph); +} + + +/* end of merchant_api_post_templates.c */ diff --git a/src/lib/merchant_api_post_tokenfamilies.c b/src/lib/merchant_api_post_tokenfamilies.c new file mode 100644 index 00000000..0c5e18c2 --- /dev/null +++ b/src/lib/merchant_api_post_tokenfamilies.c @@ -0,0 +1,246 @@ +/* + 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 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_post_tokenfamilies.c + * @brief Implementation of the POST /tokenfamilies request + * of the merchant's HTTP API + * @author Christian Blättler + */ +#include "platform.h" +#include <curl/curl.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_time_lib.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a POST /tokenfamilies operation. + */ +struct TALER_MERCHANT_TokenFamiliesPostHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_TokenFamiliesPostCallback 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 + * HTTP POST /tokenfamilies request. + * + * @param cls the `struct TALER_MERCHANT_TokenFamiliesPostHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_token_families_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_TokenFamiliesPostHandle *handle = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + handle->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "POST /tokenfamilies completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + handle->cb (handle->cb_cls, + &hr); + TALER_MERCHANT_token_families_post_cancel (handle); +} + +struct TALER_MERCHANT_TokenFamiliesPostHandle * +TALER_MERCHANT_token_families_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *slug, + const char *name, + const char *description, + const json_t *description_i18n, + struct GNUNET_TIME_Timestamp valid_after, + struct GNUNET_TIME_Timestamp valid_before, + struct GNUNET_TIME_Relative duration, + const char *kind, + TALER_MERCHANT_TokenFamiliesPostCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_TokenFamiliesPostHandle *handle; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("slug", + slug), + GNUNET_JSON_pack_string ("name", + name), + GNUNET_JSON_pack_string ("description", + description), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + (json_t *) description_i18n)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("valid_after", + valid_after)), + GNUNET_JSON_pack_timestamp ("valid_before", + valid_before), + GNUNET_JSON_pack_time_rel ("duration", + duration), + GNUNET_JSON_pack_string ("kind", + kind)); + handle = GNUNET_new (struct TALER_MERCHANT_TokenFamiliesPostHandle); + handle->ctx = ctx; + handle->cb = cb; + handle->cb_cls = cb_cls; + handle->url = TALER_url_join (backend_url, + "private/tokenfamilies", + NULL); + if (NULL == handle->url) + { + + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (handle); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (handle->url); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&handle->post_ctx, + eh, + req_obj)); + json_decref (req_obj); + handle->job = GNUNET_CURL_job_add2 (ctx, + eh, + handle->post_ctx.headers, + &handle_post_token_families_finished, + handle); + GNUNET_assert (NULL != handle->job); + } + return handle; +} + + +void +TALER_MERCHANT_token_families_post_cancel ( + struct TALER_MERCHANT_TokenFamiliesPostHandle *pph) +{ + if (NULL != pph->job) + { + GNUNET_CURL_job_cancel (pph->job); + pph->job = NULL; + } + TALER_curl_easy_post_finished (&pph->post_ctx); + GNUNET_free (pph->url); + GNUNET_free (pph); +} diff --git a/src/lib/merchant_api_post_transfers.c b/src/lib/merchant_api_post_transfers.c index 01144833..615453fa 100644 --- a/src/lib/merchant_api_post_transfers.c +++ b/src/lib/merchant_api_post_transfers.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 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 @@ -27,6 +27,7 @@ #include <gnunet/gnunet_util_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_curl_lib.h> #include <taler/taler_json_lib.h> @@ -84,116 +85,22 @@ handle_post_transfers_finished (void *cls, const void *response) { struct TALER_MERCHANT_PostTransfersHandle *pth = cls; - const json_t *json = response; - json_t *deposit_sum = NULL; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_PostTransfersResponse ptr = { + .hr.reply = response, + .hr.http_status = (unsigned int) response_code }; pth->job = NULL; switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; - case MHD_HTTP_OK: - { - struct TALER_Amount total; - struct TALER_Amount wire_fee; - struct GNUNET_TIME_Timestamp execution_time; - json_t *deposit_sums; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("total", - &total), - TALER_JSON_spec_amount_any ("wire_fee", - &wire_fee), - GNUNET_JSON_spec_timestamp ("execution_time", - &execution_time), - GNUNET_JSON_spec_json ("deposit_sums", - &deposit_sums), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - else - { - size_t deposit_sums_length; - struct TALER_MERCHANT_TrackTransferDetail *details; - unsigned int i; - bool ok; - - if (! json_is_array (deposit_sums)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - deposit_sums_length = json_array_size (deposit_sums); - details = GNUNET_new_array (deposit_sums_length, - struct TALER_MERCHANT_TrackTransferDetail); - ok = true; - json_array_foreach (deposit_sums, i, deposit_sum) { - struct TALER_MERCHANT_TrackTransferDetail *d = &details[i]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("order_id", - &d->order_id), - TALER_JSON_spec_amount_any ("deposit_value", - &d->deposit_value), - TALER_JSON_spec_amount_any ("deposit_fee", - &d->deposit_fee), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (deposit_sum, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - ok = false; - break; - } - } - - if (! ok) - { - GNUNET_break_op (0); - GNUNET_free (details); - GNUNET_JSON_parse_free (spec); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - pth->cb (pth->cb_cls, - &hr, - execution_time, - &total, - &wire_fee, - deposit_sums_length, - details); - GNUNET_free (details); - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_transfers_post_cancel (pth); - return; - } - } - case MHD_HTTP_ACCEPTED: + case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply); + ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply); /* Nothing really to verify, merchant says we need to authenticate. */ break; case MHD_HTTP_NOT_FOUND: @@ -201,44 +108,47 @@ handle_post_transfers_finished (void *cls, happen, we should pass the JSON reply to the application */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Did not find any data\n"); - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply); + ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply); + ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply); break; case MHD_HTTP_BAD_GATEWAY: /* Exchange had an issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply); + ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply); { - uint32_t eec; uint32_t ehc; struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_uint32 ("exchange_code", - &eec), + TALER_JSON_spec_ec ("exchange_code", + &ptr.details.bad_gateway.exchange_ec), GNUNET_JSON_spec_uint32 ("exchange_http_status", &ehc), GNUNET_JSON_spec_end () }; if (GNUNET_OK != - GNUNET_JSON_parse (deposit_sum, + GNUNET_JSON_parse (ptr.hr.reply, ispec, NULL, NULL)) { GNUNET_break_op (0); + ptr.details.bad_gateway.exchange_http_status = 0; + ptr.details.bad_gateway.exchange_ec = TALER_EC_NONE; break; } else { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + ptr.details.bad_gateway.exchange_http_status + = (unsigned int) ehc; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Exchange returned %u/%u\n", - (unsigned int) eec, + (unsigned int) ptr.details.bad_gateway.exchange_ec, (unsigned int) ehc); } } @@ -246,28 +156,23 @@ handle_post_transfers_finished (void *cls, case MHD_HTTP_GATEWAY_TIMEOUT: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + ptr.hr.ec = TALER_JSON_get_error_code (ptr.hr.reply); + ptr.hr.hint = TALER_JSON_get_error_hint (ptr.hr.reply); break; default: /* unexpected response code */ GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, + TALER_MERCHANT_parse_error_details_ (ptr.hr.reply, response_code, - &hr); + &ptr.hr); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); + (unsigned int) ptr.hr.http_status, + (int) ptr.hr.ec); break; } pth->cb (pth->cb_cls, - &hr, - GNUNET_TIME_UNIT_FOREVER_TS, - NULL, - NULL, - 0, - NULL); + &ptr); TALER_MERCHANT_transfers_post_cancel (pth); } diff --git a/src/lib/merchant_api_post_using_templates.c b/src/lib/merchant_api_post_using_templates.c new file mode 100644 index 00000000..f09c34cb --- /dev/null +++ b/src/lib/merchant_api_post_using_templates.c @@ -0,0 +1,177 @@ +/* + This file is part of TALER + Copyright (C) 2022-2023 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_post_using_templates.c + * @brief Implementation of the POST /using_templates request + * of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_common.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a POST /templates/$ID operation. + */ +struct TALER_MERCHANT_UsingTemplatesPostHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_PostOrdersCallback 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 + * HTTP POST /using-templates request. + * + * @param cls the `struct TALER_MERCHANT_UsingTemplatesPostHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_using_templates_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_UsingTemplatesPostHandle *utph = cls; + const json_t *json = response; + + utph->job = NULL; + TALER_MERCHANT_handle_order_creation_response_ (utph->cb, + utph->cb_cls, + response_code, + json); + TALER_MERCHANT_using_templates_post_cancel (utph); +} + + +struct TALER_MERCHANT_UsingTemplatesPostHandle * +TALER_MERCHANT_using_templates_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *template_id, + const char *summary, + const struct TALER_Amount *amount, + TALER_MERCHANT_PostOrdersCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_UsingTemplatesPostHandle *utph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("summary", + summary)), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("amount", + amount))); + utph = GNUNET_new (struct TALER_MERCHANT_UsingTemplatesPostHandle); + utph->ctx = ctx; + utph->cb = cb; + utph->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "templates/%s", + template_id); + utph->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == utph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (utph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (utph->url); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&utph->post_ctx, + eh, + req_obj)); + json_decref (req_obj); + utph->job = GNUNET_CURL_job_add2 (ctx, + eh, + utph->post_ctx.headers, + &handle_post_using_templates_finished, + utph); + GNUNET_assert (NULL != utph->job); + } + return utph; +} + + +void +TALER_MERCHANT_using_templates_post_cancel ( + struct TALER_MERCHANT_UsingTemplatesPostHandle *utph) +{ + if (NULL != utph->job) + { + GNUNET_CURL_job_cancel (utph->job); + utph->job = NULL; + } + TALER_curl_easy_post_finished (&utph->post_ctx); + GNUNET_free (utph->url); + GNUNET_free (utph); +} + + +/* end of merchant_api_post_using_templates.c */ diff --git a/src/lib/merchant_api_post_webhooks.c b/src/lib/merchant_api_post_webhooks.c new file mode 100644 index 00000000..a23ce2c3 --- /dev/null +++ b/src/lib/merchant_api_post_webhooks.c @@ -0,0 +1,240 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_post_webhooks.c + * @brief Implementation of the POST /webhooks request + * of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> + + +/** + * Handle for a POST /webhooks/$ID operation. + */ +struct TALER_MERCHANT_WebhooksPostHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_WebhooksPostCallback 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 + * HTTP POST /webhooks request. + * + * @param cls the `struct TALER_MERCHANT_WebhooksPostHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_webhooks_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_WebhooksPostHandle *wph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + wph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "POST /webhooks completed with response code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case 0: + hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + wph->cb (wph->cb_cls, + &hr); + TALER_MERCHANT_webhooks_post_cancel (wph); +} + + +struct TALER_MERCHANT_WebhooksPostHandle * +TALER_MERCHANT_webhooks_post ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *webhook_id, + const char *event_type, + const char *url, + const char *http_method, + const char *header_template, + const char *body_template, + TALER_MERCHANT_WebhooksPostCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_WebhooksPostHandle *wph; + json_t *req_obj; + + req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("webhook_id", + webhook_id), + GNUNET_JSON_pack_string ("event_type", + event_type), + GNUNET_JSON_pack_string ("url", + url), + GNUNET_JSON_pack_string ("http_method", + http_method), + GNUNET_JSON_pack_string ("header_template", + header_template), + GNUNET_JSON_pack_string ("body_template", + body_template)); + wph = GNUNET_new (struct TALER_MERCHANT_WebhooksPostHandle); + wph->ctx = ctx; + wph->cb = cb; + wph->cb_cls = cb_cls; + wph->url = TALER_url_join (backend_url, + "private/webhooks", + NULL); + if (NULL == wph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (req_obj); + GNUNET_free (wph); + return NULL; + } + { + CURL *eh; + + eh = TALER_MERCHANT_curl_easy_get_ (wph->url); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&wph->post_ctx, + eh, + req_obj)); + json_decref (req_obj); + wph->job = GNUNET_CURL_job_add2 (ctx, + eh, + wph->post_ctx.headers, + &handle_post_webhooks_finished, + wph); + GNUNET_assert (NULL != wph->job); + } + return wph; +} + + +void +TALER_MERCHANT_webhooks_post_cancel ( + struct TALER_MERCHANT_WebhooksPostHandle *wph) +{ + if (NULL != wph->job) + { + GNUNET_CURL_job_cancel (wph->job); + wph->job = NULL; + } + TALER_curl_easy_post_finished (&wph->post_ctx); + GNUNET_free (wph->url); + GNUNET_free (wph); +} + + +/* end of merchant_api_post_webhooks.c */ diff --git a/src/lib/merchant_api_tip_authorize.c b/src/lib/merchant_api_tip_authorize.c deleted file mode 100644 index f8990b4c..00000000 --- a/src/lib/merchant_api_tip_authorize.c +++ /dev/null @@ -1,375 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2017, 2020, 2021 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_tip_authorize.c - * @brief Implementation of the /tip-authorize request of the merchant's HTTP API - * @author Marcello Stanisci - * @author Christian Grothoff - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> -#include <taler/taler_curl_lib.h> - - -/** - * @brief A handle for tip authorizations. - */ -struct TALER_MERCHANT_TipAuthorizeHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_TipAuthorizeCallback 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; -}; - - -/** - * We got a 200 response back from the exchange (or the merchant). - * Now we need to parse the response and if it is well-formed, - * call the callback (and set it to NULL afterwards). - * - * @param tao handle of the original authorization operation - * @param json cryptographic proof returned by the exchange/merchant - * @return #GNUNET_OK if response is valid - */ -static int -check_ok (struct TALER_MERCHANT_TipAuthorizeHandle *tao, - const json_t *json) -{ - const char *tip_status_url; - const char *taler_tip_uri; - struct TALER_TipIdentifierP tip_id; - struct GNUNET_TIME_Timestamp expiration_time; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("tip_status_url", &tip_status_url), - GNUNET_JSON_spec_string ("taler_tip_uri", &taler_tip_uri), - GNUNET_JSON_spec_timestamp ("tip_expiration", &expiration_time), - GNUNET_JSON_spec_fixed_auto ("tip_id", &tip_id), - GNUNET_JSON_spec_end () - }; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = MHD_HTTP_OK, - .reply = json - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - char *log; - - GNUNET_break_op (0); - log = json_dumps (json, - 0); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "JSON %s\n", - log); - free (log); - return GNUNET_SYSERR; - } - tao->cb (tao->cb_cls, - &hr, - &tip_id, - taler_tip_uri, - expiration_time); - tao->cb = NULL; /* do not call twice */ - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reservers/$TIP_ID/tip-authorize request. - * - * @param cls the `struct TALER_MERCHANT_TipAuthorizeHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_tip_authorize_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_TipAuthorizeHandle *tao = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - tao->job = NULL; - switch (response_code) - { - case MHD_HTTP_OK: - if (GNUNET_OK == - check_ok (tao, - json)) - { - TALER_MERCHANT_tip_authorize_cancel (tao); - return; - } - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_UNAUTHORIZED: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we need to authenticate. */ - break; - case MHD_HTTP_NOT_FOUND: - /* Well-defined status code, pass on to application! */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_PRECONDITION_FAILED: - /* Well-defined status code, pass on to application! */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_SERVICE_UNAVAILABLE: - /* Server had an unclear (internal or external) issue; we should retry, - but this API leaves this to the application */ - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - break; - } - if (NULL != tao->cb) - tao->cb (tao->cb_cls, - &hr, - NULL, - NULL, - GNUNET_TIME_UNIT_ZERO_TS); - TALER_MERCHANT_tip_authorize_cancel (tao); -} - - -struct TALER_MERCHANT_TipAuthorizeHandle * -TALER_MERCHANT_tip_authorize2 ( - struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *next_url, - const struct TALER_Amount *amount, - const char *justification, - TALER_MERCHANT_TipAuthorizeCallback authorize_cb, - void *authorize_cb_cls) -{ - struct TALER_MERCHANT_TipAuthorizeHandle *tao; - CURL *eh; - json_t *te_obj; - - tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeHandle); - tao->ctx = ctx; - tao->cb = authorize_cb; - tao->cb_cls = authorize_cb_cls; - - { - char res_str[sizeof (*reserve_pub) * 2]; - char arg_str[sizeof (res_str) + 48]; - char *end; - - end = GNUNET_STRINGS_data_to_string (reserve_pub, - sizeof (*reserve_pub), - res_str, - sizeof (res_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "private/reserves/%s/authorize-tip", - res_str); - tao->url = TALER_url_join (backend_url, - arg_str, - NULL); - } - if (NULL == tao->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (tao); - return NULL; - } - te_obj = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - amount), - GNUNET_JSON_pack_string ("justification", - justification), - GNUNET_JSON_pack_string ("next_url", - next_url)); - eh = curl_easy_init (); - GNUNET_assert (NULL != eh); - if (GNUNET_OK != - TALER_curl_easy_post (&tao->post_ctx, - eh, - te_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (te_obj); - GNUNET_free (tao->url); - GNUNET_free (tao); - return NULL; - } - - json_decref (te_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - tao->url); - GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, - CURLOPT_URL, - tao->url)); - - tao->job = GNUNET_CURL_job_add2 (ctx, - eh, - tao->post_ctx.headers, - &handle_tip_authorize_finished, - tao); - return tao; -} - - -struct TALER_MERCHANT_TipAuthorizeHandle * -TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const char *next_url, - const struct TALER_Amount *amount, - const char *justification, - TALER_MERCHANT_TipAuthorizeCallback authorize_cb, - void *authorize_cb_cls) -{ - struct TALER_MERCHANT_TipAuthorizeHandle *tao; - CURL *eh; - json_t *te_obj; - - tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeHandle); - tao->ctx = ctx; - tao->cb = authorize_cb; - tao->cb_cls = authorize_cb_cls; - - tao->url = TALER_url_join (backend_url, - "private/tips", - NULL); - if (NULL == tao->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (tao); - return NULL; - } - te_obj = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - amount), - GNUNET_JSON_pack_string ("justification", - justification), - GNUNET_JSON_pack_string ("next_url", - next_url)); - eh = TALER_MERCHANT_curl_easy_get_ (tao->url); - if (GNUNET_OK != - TALER_curl_easy_post (&tao->post_ctx, - eh, - te_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (te_obj); - GNUNET_free (tao->url); - GNUNET_free (tao); - return NULL; - } - json_decref (te_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - tao->url); - tao->job = GNUNET_CURL_job_add2 (ctx, - eh, - tao->post_ctx.headers, - &handle_tip_authorize_finished, - tao); - return tao; -} - - -void -TALER_MERCHANT_tip_authorize_cancel ( - struct TALER_MERCHANT_TipAuthorizeHandle *tao) -{ - if (NULL != tao->job) - { - GNUNET_CURL_job_cancel (tao->job); - tao->job = NULL; - } - TALER_curl_easy_post_finished (&tao->post_ctx); - GNUNET_free (tao->url); - GNUNET_free (tao); -} - - -/* end of merchant_api_tip_authorize.c */ diff --git a/src/lib/merchant_api_tip_pickup.c b/src/lib/merchant_api_tip_pickup.c deleted file mode 100644 index 593efa43..00000000 --- a/src/lib/merchant_api_tip_pickup.c +++ /dev/null @@ -1,446 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2022 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_tip_pickup.c - * @brief Implementation of the /tip-pickup request of the merchant's HTTP API - * @author Marcello Stanisci - * @author Christian Grothoff - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> -#include <taler/taler_curl_lib.h> - - -/** - * Data we keep per planchet. - */ -struct PlanchetData -{ - /** - * Secrets of the planchet. - */ - struct TALER_PlanchetMasterSecretP ps; - - /** - * Denomination key we are withdrawing. - */ - struct TALER_EXCHANGE_DenomPublicKey pk; - - /** - * Hash of the public key of the coin we are signing. - */ - struct TALER_CoinPubHashP c_hash; - - /** - * Nonce used for @e csr request, if any. - */ - struct TALER_CsNonce nonce; - - /** - * Handle for a /csr request we may optionally need - * to trigger. - */ - struct TALER_EXCHANGE_CsRWithdrawHandle *csr; - - /** - * Handle for the /tip-pickup operation we are part of. - */ - struct TALER_MERCHANT_TipPickupHandle *tp; - - /** - * Offset of this entry in the array. - */ - unsigned int off; -}; - - -/** - * Handle for a /tip-pickup operation. - */ -struct TALER_MERCHANT_TipPickupHandle -{ - - /** - * Function to call with the result. - */ - TALER_MERCHANT_TipPickupCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Handle for the actual (internal) withdraw operation. - */ - struct TALER_MERCHANT_TipPickup2Handle *tpo2; - - /** - * Array of length @e num_planchets. - */ - struct PlanchetData *planchets; - - /** - * Array of length @e num_planchets. - */ - struct TALER_EXCHANGE_PrivateCoinDetails *pcds; - - /** - * Context for making HTTP requests. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * URL of the merchant backend. - */ - char *backend_url; - - /** - * ID of the tip we are picking up. - */ - struct TALER_TipIdentifierP tip_id; - - /** - * Number of planchets/coins used for this operation. - */ - unsigned int num_planchets; - - /** - * Number of remaining active /csr-withdraw requests. - */ - unsigned int csr_active; -}; - - -/** - * Fail the pickup operation @a tp, returning @a ec. - * Also cancels @a tp. - * - * @param[in] tp operation to fail - * @param ec reason for the failure - */ -static void -fail_pickup (struct TALER_MERCHANT_TipPickupHandle *tp, - enum TALER_ErrorCode ec) -{ - struct TALER_MERCHANT_PickupDetails pd = { - .hr.ec = ec - }; - - tp->cb (tp->cb_cls, - &pd); - TALER_MERCHANT_tip_pickup_cancel (tp); -} - - -/** - * Callback for a /tip-pickup request. Returns the result of the operation. - * Note that the client MUST still do the unblinding of the @a blind_sigs. - * - * @param cls closure, a `struct TALER_MERCHANT_TipPickupHandle *` - * @param hr HTTP response details - * @param num_blind_sigs length of the @a reserve_sigs array, 0 on error - * @param blind_sigs array of blind signatures over the planchets, NULL on error - */ -static void -pickup_done_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - unsigned int num_blind_sigs, - const struct TALER_BlindedDenominationSignature *blind_sigs) -{ - struct TALER_MERCHANT_TipPickupHandle *tp = cls; - struct TALER_MERCHANT_PickupDetails pd = { - .hr = *hr - }; - - tp->tpo2 = NULL; - if (NULL == blind_sigs) - { - tp->cb (tp->cb_cls, - &pd); - TALER_MERCHANT_tip_pickup_cancel (tp); - return; - } - { - enum GNUNET_GenericReturnValue ok = GNUNET_OK; - - for (unsigned int i = 0; i<num_blind_sigs; i++) - { - struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; - struct TALER_FreshCoin fc; - - if (GNUNET_OK != - TALER_planchet_to_coin (&tp->planchets[i].pk.key, - &blind_sigs[i], - &pcd->bks, - &pcd->coin_priv, - NULL, - &tp->planchets[i].c_hash, - &pcd->exchange_vals, - &fc)) - { - ok = GNUNET_SYSERR; - break; - } - pcd->sig = fc.sig; - } - if (GNUNET_OK != ok) - { - struct TALER_MERCHANT_HttpResponse hrx = { - .reply = hr->reply, - .ec = TALER_EC_MERCHANT_TIP_PICKUP_UNBLIND_FAILURE - }; - - pd.hr = hrx; - tp->cb (tp->cb_cls, - &pd); - } - else - { - pd.details.success.num_sigs = num_blind_sigs; - pd.details.success.pcds = tp->pcds; - tp->cb (tp->cb_cls, - &pd); - } - } - TALER_MERCHANT_tip_pickup_cancel (tp); -} - - -/** - * We have obtained all of the exchange inputs. Continue the pickup. - * - * @param[in,out] tp operation to continue - */ -static void -pickup_post_csr (struct TALER_MERCHANT_TipPickupHandle *tp) -{ - struct TALER_PlanchetDetail details[tp->num_planchets]; - - for (unsigned int i = 0; i<tp->num_planchets; i++) - { - const struct PlanchetData *pd = &tp->planchets[i]; - struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; - - TALER_planchet_setup_coin_priv (&pd->ps, - &pcd->exchange_vals, - &pcd->coin_priv); - TALER_planchet_blinding_secret_create (&pd->ps, - &pcd->exchange_vals, - &pcd->bks); - if (TALER_DENOMINATION_CS == pcd->exchange_vals.cipher) - { - details[i].blinded_planchet.details.cs_blinded_planchet.nonce - = pd->nonce; - } - if (GNUNET_OK != - TALER_planchet_prepare (&pd->pk.key, - &pcd->exchange_vals, - &pcd->bks, - &pcd->coin_priv, - NULL, - &tp->planchets[i].c_hash, - &details[i])) - { - GNUNET_break (0); - for (unsigned int j = 0; j<i; j++) - TALER_planchet_detail_free (&details[j]); - fail_pickup (tp, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE); - return; - } - } - tp->tpo2 = TALER_MERCHANT_tip_pickup2 (tp->ctx, - tp->backend_url, - &tp->tip_id, - tp->num_planchets, - details, - &pickup_done_cb, - tp); - for (unsigned int j = 0; j<tp->num_planchets; j++) - TALER_planchet_detail_free (&details[j]); - if (NULL == tp->tpo2) - { - GNUNET_break (0); - fail_pickup (tp, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE); - return; - } -} - - -/** - * Callbacks of this type are used to serve the result of submitting a - * CS R request to a exchange. - * - * @param cls a `struct TALER_MERCHANT_TipPickupHandle` - * @param csrr response details - */ -static void -csr_cb (void *cls, - const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr) -{ - struct PlanchetData *pd = cls; - struct TALER_MERCHANT_TipPickupHandle *tp = pd->tp; - - pd->csr = NULL; - tp->csr_active--; - switch (csrr->hr.http_status) - { - case MHD_HTTP_OK: - { - struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[pd->off]; - - pcd->exchange_vals = csrr->details.success.alg_values; - } - if (0 != tp->csr_active) - return; - pickup_post_csr (tp); - return; - default: - { - struct TALER_MERCHANT_PickupDetails pd = { - .hr.hint = "/csr-withdraw failed", - .hr.exchange_http_status = csrr->hr.http_status - }; - - tp->cb (tp->cb_cls, - &pd); - TALER_MERCHANT_tip_pickup_cancel (tp); - return; - } - } -} - - -struct TALER_MERCHANT_TipPickupHandle * -TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx, - struct TALER_EXCHANGE_Handle *exchange, - const char *backend_url, - const struct TALER_TipIdentifierP *tip_id, - unsigned int num_planchets, - const struct TALER_MERCHANT_PlanchetData *pds, - TALER_MERCHANT_TipPickupCallback pickup_cb, - void *pickup_cb_cls) -{ - struct TALER_MERCHANT_TipPickupHandle *tp; - - if (0 == num_planchets) - { - GNUNET_break (0); - return NULL; - } - tp = GNUNET_new (struct TALER_MERCHANT_TipPickupHandle); - tp->cb = pickup_cb; - tp->cb_cls = pickup_cb_cls; - tp->ctx = ctx; - tp->backend_url = GNUNET_strdup (backend_url); - tp->tip_id = *tip_id; - tp->num_planchets = num_planchets; - tp->planchets = GNUNET_new_array (num_planchets, - struct PlanchetData); - tp->pcds = GNUNET_new_array (num_planchets, - struct TALER_EXCHANGE_PrivateCoinDetails); - for (unsigned int i = 0; i<num_planchets; i++) - { - const struct TALER_MERCHANT_PlanchetData *mpd = &pds[i]; - const struct TALER_EXCHANGE_DenomPublicKey *pk = mpd->pk; - struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; - struct PlanchetData *pd = &tp->planchets[i]; - - pd->off = i; - pd->tp = tp; - tp->planchets[i].ps = mpd->ps; - tp->planchets[i].pk = *pds[i].pk; - TALER_denom_pub_deep_copy (&tp->planchets[i].pk.key, - &pds[i].pk->key); - switch (pk->key.cipher) - { - case TALER_DENOMINATION_RSA: - pcd->exchange_vals.cipher = TALER_DENOMINATION_RSA; - break; - case TALER_DENOMINATION_CS: - { - TALER_cs_withdraw_nonce_derive (&pd->ps, - &pd->nonce); - pd->csr = TALER_EXCHANGE_csr_withdraw (exchange, - &pd->pk, - &pd->nonce, - &csr_cb, - pd); - if (NULL == pd->csr) - { - GNUNET_break (0); - TALER_MERCHANT_tip_pickup_cancel (tp); - return NULL; - } - tp->csr_active++; - break; - } - default: - GNUNET_break (0); - TALER_MERCHANT_tip_pickup_cancel (tp); - return NULL; - } - } - if (0 == tp->csr_active) - { - pickup_post_csr (tp); - return tp; - } - return tp; -} - - -void -TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupHandle *tp) -{ - for (unsigned int i = 0; i<tp->num_planchets; i++) - { - struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; - struct PlanchetData *pd = &tp->planchets[i]; - - TALER_denom_sig_free (&pcd->sig); - TALER_denom_pub_free (&tp->planchets[i].pk.key); - if (NULL != pd->csr) - { - TALER_EXCHANGE_csr_withdraw_cancel (pd->csr); - pd->csr = NULL; - } - } - GNUNET_array_grow (tp->planchets, - tp->num_planchets, - 0); - if (NULL != tp->tpo2) - { - TALER_MERCHANT_tip_pickup2_cancel (tp->tpo2); - tp->tpo2 = NULL; - } - GNUNET_free (tp->backend_url); - GNUNET_free (tp->pcds); - GNUNET_free (tp); -} - - -/* end of merchant_api_tip_pickup.c */ diff --git a/src/lib/merchant_api_tip_pickup2.c b/src/lib/merchant_api_tip_pickup2.c deleted file mode 100644 index cd1a0c83..00000000 --- a/src/lib/merchant_api_tip_pickup2.c +++ /dev/null @@ -1,350 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2021 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_tip_pickup2.c - * @brief Implementation of the /tip-pickup request of the merchant's HTTP API - * @author Marcello Stanisci - * @author Christian Grothoff - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> -#include <taler/taler_curl_lib.h> - - -/** - * @brief A handle for tracking transactions. - */ -struct TALER_MERCHANT_TipPickup2Handle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_TipPickup2Callback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Expected number of planchets. - */ - unsigned int num_planchets; -}; - - -/** - * We got a 200 response back from the exchange (or the merchant). - * Now we need to parse the response and if it is well-formed, - * call the callback (and set it to NULL afterwards). - * - * @param tpo handle of the original authorization operation - * @param json cryptographic proof returned by the exchange/merchant - * @return #GNUNET_OK if response is valid - */ -static int -check_ok (struct TALER_MERCHANT_TipPickup2Handle *tpo, - const json_t *json) -{ - json_t *ja; - unsigned int ja_len; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("blind_sigs", &ja), - GNUNET_JSON_spec_end () - }; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = MHD_HTTP_OK, - .reply = json - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - ja_len = json_array_size (ja); - if (ja_len != tpo->num_planchets) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - { - struct TALER_BlindedDenominationSignature mblind_sigs[ja_len]; - - for (unsigned int i = 0; i<ja_len; i++) - { - json_t *pj = json_array_get (ja, i); - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_blinded_denom_sig ("blind_sig", - &mblind_sigs[i]), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (pj, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - } - tpo->cb (tpo->cb_cls, - &hr, - ja_len, - mblind_sigs); - for (unsigned int i = 0; i<ja_len; i++) - TALER_blinded_denom_sig_free (&mblind_sigs[i]); - tpo->cb = NULL; /* do not call twice */ - } - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /track/transaction request. - * - * @param cls the `struct TALER_MERCHANT_TipPickupHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_tip_pickup_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_TipPickup2Handle *tpo = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - tpo->job = NULL; - switch (response_code) - { - case MHD_HTTP_OK: - if (GNUNET_OK != check_ok (tpo, - json)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* Can happen if we pickup an amount that exceeds the tip... */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_break (TALER_EC_MERCHANT_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING == - hr.ec); - break; - case MHD_HTTP_CONFLICT: - /* legal, can happen if we pickup a tip twice... */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - /* legal, can happen if tip ID is unknown */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - break; - } - if (NULL != tpo->cb) - { - tpo->cb (tpo->cb_cls, - &hr, - 0, - NULL); - tpo->cb = NULL; - } - TALER_MERCHANT_tip_pickup2_cancel (tpo); -} - - -struct TALER_MERCHANT_TipPickup2Handle * -TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_TipIdentifierP *tip_id, - unsigned int num_planchets, - const struct TALER_PlanchetDetail planchets[], - TALER_MERCHANT_TipPickup2Callback pickup_cb, - void *pickup_cb_cls) -{ - struct TALER_MERCHANT_TipPickup2Handle *tpo; - CURL *eh; - json_t *pa; - json_t *tp_obj; - - if (0 == num_planchets) - { - GNUNET_break (0); - return NULL; - } - pa = json_array (); - for (unsigned int i = 0; i<num_planchets; i++) - { - const struct TALER_PlanchetDetail *planchet = &planchets[i]; - json_t *p; - - p = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &planchet->denom_pub_hash), - TALER_JSON_pack_blinded_planchet ("coin_ev", - &planchet->blinded_planchet)); - if (0 != - json_array_append_new (pa, - p)) - { - GNUNET_break (0); - json_decref (pa); - return NULL; - } - } - tp_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_steal ("planchets", - pa)); - tpo = GNUNET_new (struct TALER_MERCHANT_TipPickup2Handle); - tpo->num_planchets = num_planchets; - tpo->ctx = ctx; - tpo->cb = pickup_cb; - tpo->cb_cls = pickup_cb_cls; - - { - char tip_str[sizeof (*tip_id) * 2]; - char arg_str[sizeof (tip_str) + 32]; - char *end; - - end = GNUNET_STRINGS_data_to_string (tip_id, - sizeof (*tip_id), - tip_str, - sizeof (tip_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "tips/%s/pickup", - tip_str); - tpo->url = TALER_url_join (backend_url, - arg_str, - NULL); - } - if (NULL == tpo->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - json_decref (tp_obj); - GNUNET_free (tpo); - return NULL; - } - eh = TALER_MERCHANT_curl_easy_get_ (tpo->url); - if (GNUNET_OK != TALER_curl_easy_post (&tpo->post_ctx, - eh, - tp_obj)) - { - GNUNET_break (0); - json_decref (tp_obj); - GNUNET_free (tpo->url); - GNUNET_free (tpo); - return NULL; - } - json_decref (tp_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - tpo->url); - tpo->job = GNUNET_CURL_job_add2 (ctx, - eh, - tpo->post_ctx.headers, - &handle_tip_pickup_finished, - tpo); - if (NULL == tpo->job) - { - TALER_MERCHANT_tip_pickup2_cancel (tpo); - return NULL; - } - return tpo; -} - - -void -TALER_MERCHANT_tip_pickup2_cancel ( - struct TALER_MERCHANT_TipPickup2Handle *tpo) -{ - if (NULL != tpo->job) - { - GNUNET_CURL_job_cancel (tpo->job); - tpo->job = NULL; - } - TALER_curl_easy_post_finished (&tpo->post_ctx); - GNUNET_free (tpo->url); - GNUNET_free (tpo); -} - - -/* end of merchant_api_tip_pickup2.c */ diff --git a/src/lib/merchant_api_wallet_get_order.c b/src/lib/merchant_api_wallet_get_order.c index 4037ea57..763b2c83 100644 --- a/src/lib/merchant_api_wallet_get_order.c +++ b/src/lib/merchant_api_wallet_get_order.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2018, 2020 Taler Systems SA + Copyright (C) 2018, 2020, 2022 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 @@ -29,6 +29,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" #include "merchant_api_curl_defaults.h" +#include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> @@ -79,19 +80,13 @@ cb_failure (struct TALER_MERCHANT_OrderWalletGetHandle *owgh, enum TALER_ErrorCode ec, const json_t *reply) { - struct TALER_MERCHANT_HttpResponse hr = { - .ec = ec, - .reply = reply + struct TALER_MERCHANT_OrderWalletGetResponse owgr = { + .hr.ec = ec, + .hr.reply = reply }; owgh->cb (owgh->cb_cls, - &hr, - GNUNET_SYSERR, - GNUNET_SYSERR, - GNUNET_SYSERR, - NULL, - NULL, - NULL); + &owgr); } @@ -115,22 +110,19 @@ handle_wallet_get_order_finished (void *cls, { case MHD_HTTP_OK: { - struct TALER_Amount refund_amount; - bool refunded; - bool refund_pending; + struct TALER_MERCHANT_OrderWalletGetResponse owgr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_bool ("refunded", - &refunded), + &owgr.details.ok.refunded), GNUNET_JSON_spec_bool ("refund_pending", - &refund_pending), + &owgr.details.ok.refund_pending), TALER_JSON_spec_amount_any ("refund_amount", - &refund_amount), + &owgr.details.ok.refund_amount), GNUNET_JSON_spec_end () }; - struct TALER_MERCHANT_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; if (GNUNET_OK != GNUNET_JSON_parse (json, @@ -144,70 +136,54 @@ handle_wallet_get_order_finished (void *cls, TALER_MERCHANT_wallet_order_get_cancel (owgh); return; } - owgh->cb (owgh->cb_cls, - &hr, - GNUNET_YES, - refunded ? GNUNET_YES : GNUNET_NO, - refund_pending ? GNUNET_YES : GNUNET_NO, - refunded ? &refund_amount : NULL, - NULL, /* paid! */ - NULL);/* paid! */ + &owgr); GNUNET_JSON_parse_free (spec); break; } case MHD_HTTP_PAYMENT_REQUIRED: { + struct TALER_MERCHANT_OrderWalletGetResponse owgr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED + }; + /* Status is: unpaid */ - const char *taler_pay_uri = json_string_value (json_object_get (json, - "taler_pay_uri")); - const char *already_paid = json_string_value (json_object_get (json, - "already_paid_order_id")); - if (NULL == taler_pay_uri) + owgr.details.payment_required.taler_pay_uri + = json_string_value (json_object_get (json, + "taler_pay_uri")); + owgr.details.payment_required.already_paid_order_id + = json_string_value (json_object_get (json, + "already_paid_order_id")); + if (NULL == owgr.details.payment_required.taler_pay_uri) { GNUNET_break_op (0); cb_failure (owgh, TALER_EC_GENERIC_REPLY_MALFORMED, json); + break; } - else - { - struct TALER_MERCHANT_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; - - owgh->cb (owgh->cb_cls, - &hr, - GNUNET_NO, - GNUNET_NO, - GNUNET_NO, - NULL, - taler_pay_uri, - already_paid); - } + owgh->cb (owgh->cb_cls, + &owgr); break; } default: { - struct TALER_MERCHANT_HttpResponse hr; + struct TALER_MERCHANT_OrderWalletGetResponse owgr = { + .hr.reply = json, + .hr.http_status = response_code + }; TALER_MERCHANT_parse_error_details_ (response, response_code, - &hr); + &owgr.hr); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Checking order status failed with HTTP status code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) owgr.hr.ec); GNUNET_break_op (0); owgh->cb (owgh->cb_cls, - &hr, - GNUNET_SYSERR, - GNUNET_SYSERR, - GNUNET_SYSERR, - NULL, - NULL, - NULL); + &owgr); break; } } @@ -229,8 +205,7 @@ TALER_MERCHANT_wallet_order_get ( void *cb_cls) { struct TALER_MERCHANT_OrderWalletGetHandle *owgh; - unsigned long long tms; - long tlong; + unsigned int tms; GNUNET_assert (NULL != backend_url); GNUNET_assert (NULL != order_id); @@ -238,14 +213,8 @@ TALER_MERCHANT_wallet_order_get ( owgh->ctx = ctx; owgh->cb = cb; owgh->cb_cls = cb_cls; - tms = (unsigned long long) (timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); - /* set curl timeout to *our* long poll timeout plus one minute - (for network latency and processing delays) */ - tlong = (long) (GNUNET_TIME_relative_add (timeout, - GNUNET_TIME_UNIT_MINUTES). - rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); + tms = (unsigned int) (timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); { char timeout_ms[32]; struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s; @@ -255,7 +224,7 @@ TALER_MERCHANT_wallet_order_get ( &h_contract_s); GNUNET_snprintf (timeout_ms, sizeof (timeout_ms), - "%llu", + "%u", tms); GNUNET_asprintf (&path, "orders/%s", @@ -295,28 +264,10 @@ TALER_MERCHANT_wallet_order_get ( eh = TALER_MERCHANT_curl_easy_get_ (owgh->url); if (0 != tms) { - if (CURLE_OK != - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - (long) tms)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - GNUNET_free (owgh->url); - GNUNET_free (owgh); - return NULL; - } - } - if (CURLE_OK != - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - tlong)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - GNUNET_free (owgh->url); - GNUNET_free (owgh); - return NULL; + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, diff --git a/src/lib/merchant_api_wallet_get_template.c b/src/lib/merchant_api_wallet_get_template.c new file mode 100644 index 00000000..d9ca95bc --- /dev/null +++ b/src/lib/merchant_api_wallet_get_template.c @@ -0,0 +1,195 @@ +/* + This file is part of TALER + Copyright (C) 2022 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_wallet_get_template.c + * @brief Implementation of the GET /templates/$ID request of the merchant's HTTP API + * @author Priscilla HUANG + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * Handle for a GET /templates/$ID operation. + */ +struct TALER_MERCHANT_WalletTemplateGetHandle +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_WalletTemplateGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + +}; + + +/** + * Function called when we're done processing the + * HTTP GET /templates/$ID request. + * + * @param cls the `struct TALER_MERCHANT_TemplateGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_template_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_WalletTemplateGetHandle *tgh = cls; + const json_t *json = response; + struct TALER_MERCHANT_WalletTemplateGetResponse tgr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + tgh->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got /templates/$ID response with status code %u\n", + (unsigned int) response_code); + switch (response_code) + { + case MHD_HTTP_OK: + { + const json_t *contract; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_object_const ("template_contract", + &contract), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK == + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + tgr.details.ok.template_contract = contract; + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_wallet_template_get_cancel (tgh); + return; + } + tgr.hr.http_status = 0; + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + case MHD_HTTP_UNAUTHORIZED: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we need to authenticate. */ + break; + case MHD_HTTP_NOT_FOUND: + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + tgr.hr.ec = TALER_JSON_get_error_code (json); + tgr.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) tgr.hr.ec); + break; + } + tgh->cb (tgh->cb_cls, + &tgr); + TALER_MERCHANT_wallet_template_get_cancel (tgh); +} + + +struct TALER_MERCHANT_WalletTemplateGetHandle * +TALER_MERCHANT_wallet_template_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *template_id, + TALER_MERCHANT_WalletTemplateGetCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_WalletTemplateGetHandle *tgh; + CURL *eh; + + tgh = GNUNET_new (struct TALER_MERCHANT_WalletTemplateGetHandle); + tgh->ctx = ctx; + tgh->cb = cb; + tgh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "templates/%s", + template_id); + tgh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == tgh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (tgh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + tgh->url); + eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); + tgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_template_finished, + tgh); + return tgh; +} + + +void +TALER_MERCHANT_wallet_template_get_cancel ( + struct TALER_MERCHANT_WalletTemplateGetHandle *tgh) +{ + if (NULL != tgh->job) + GNUNET_CURL_job_cancel (tgh->job); + GNUNET_free (tgh->url); + GNUNET_free (tgh); +} diff --git a/src/lib/merchant_api_wallet_get_tip.c b/src/lib/merchant_api_wallet_get_tip.c deleted file mode 100644 index 07a46ab7..00000000 --- a/src/lib/merchant_api_wallet_get_tip.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - 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 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_wallet_get_tip.c - * @brief Implementation of the GET /tips/$TIP_ID request of the merchant's HTTP API - * @author Florian Dold - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include "merchant_api_curl_defaults.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> - - -/** - * @brief A handle for tracking /tip-get operations - */ -struct TALER_MERCHANT_TipWalletGetHandle -{ - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_MERCHANT_TipWalletGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - -}; - - -/** - * Function called when we're done processing the - * HTTP /track/transaction request. - * - * @param cls the `struct TALER_MERCHANT_TipGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_wallet_tip_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_TipWalletGetHandle *tgh = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Got /tip/$TIP_ID response with status code %u\n", - (unsigned int) response_code); - - tgh->job = NULL; - switch (response_code) - { - case MHD_HTTP_OK: - { - const char *exchange_url; - struct TALER_Amount amount_remaining; - struct GNUNET_TIME_Timestamp expiration; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("expiration", - &expiration), - GNUNET_JSON_spec_string ("exchange_url", - &exchange_url), - TALER_JSON_spec_amount_any ("tip_amount", - &amount_remaining), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - hr.http_status = 0; - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - tgh->cb (tgh->cb_cls, - &hr, - expiration, - exchange_url, - &amount_remaining); - TALER_MERCHANT_wallet_tip_get_cancel (tgh); - return; - } - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - /* legal, can happen if instance or tip reserve is unknown */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - break; - } - tgh->cb (tgh->cb_cls, - &hr, - GNUNET_TIME_UNIT_ZERO_TS, - NULL, - NULL); - TALER_MERCHANT_wallet_tip_get_cancel (tgh); -} - - -struct TALER_MERCHANT_TipWalletGetHandle * -TALER_MERCHANT_wallet_tip_get (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_TipIdentifierP *tip_id, - TALER_MERCHANT_TipWalletGetCallback cb, - void *cb_cls) -{ - struct TALER_MERCHANT_TipWalletGetHandle *tgh; - CURL *eh; - - tgh = GNUNET_new (struct TALER_MERCHANT_TipWalletGetHandle); - tgh->ctx = ctx; - tgh->cb = cb; - tgh->cb_cls = cb_cls; - { - char res_str[sizeof (*tip_id) * 2]; - char arg_str[sizeof (res_str) + 48]; - char *end; - - end = GNUNET_STRINGS_data_to_string (tip_id, - sizeof (*tip_id), - res_str, - sizeof (res_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "tips/%s", - res_str); - tgh->url = TALER_url_join (backend_url, - arg_str, - NULL); - } - - if (NULL == tgh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (tgh); - return NULL; - } - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Requesting URL '%s'\n", - tgh->url); - eh = TALER_MERCHANT_curl_easy_get_ (tgh->url); - tgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_wallet_tip_get_finished, - tgh); - return tgh; -} - - -void -TALER_MERCHANT_wallet_tip_get_cancel ( - struct TALER_MERCHANT_TipWalletGetHandle *tgh) -{ - if (NULL != tgh->job) - { - GNUNET_CURL_job_cancel (tgh->job); - tgh->job = NULL; - } - GNUNET_free (tgh->url); - GNUNET_free (tgh); -} - - -/* end of merchant_api_wallet_get_tip.c */ diff --git a/src/lib/merchant_api_wallet_post_order_refund.c b/src/lib/merchant_api_wallet_post_order_refund.c index 1bc28e2d..e72982f3 100644 --- a/src/lib/merchant_api_wallet_post_order_refund.c +++ b/src/lib/merchant_api_wallet_post_order_refund.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2023 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 @@ -26,11 +26,16 @@ #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> #include <taler/taler_curl_lib.h> +/** + * Maximum number of refunds we return. + */ +#define MAX_REFUNDS 1024 /** * Handle for a (public) POST /orders/ID/refund operation. @@ -70,33 +75,6 @@ struct TALER_MERCHANT_WalletOrderRefundHandle /** - * Convenience function to call the callback in @a owgh with an error code of - * @a ec and the exchange body being set to @a reply. - * - * @param orh handle providing callback - * @param ec error code to return to application - * @param reply JSON reply we got from the exchange, can be NULL - */ -static void -cb_failure (struct TALER_MERCHANT_WalletOrderRefundHandle *orh, - enum TALER_ErrorCode ec, - const json_t *reply) -{ - struct TALER_MERCHANT_HttpResponse hr = { - .ec = ec, - .reply = reply - }; - - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL, - NULL, - 0); -} - - -/** * Callback to process (public) POST /orders/ID/refund response * * @param cls the `struct TALER_MERCHANT_OrderRefundHandle` @@ -110,37 +88,31 @@ handle_refund_finished (void *cls, { struct TALER_MERCHANT_WalletOrderRefundHandle *orh = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_WalletRefundResponse wrr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; orh->job = NULL; - switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL, - NULL, - 0); + wrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { - struct TALER_Amount refund_amount; - json_t *refunds; - struct TALER_MerchantPublicKeyP merchant_pub; + const json_t *refunds; unsigned int refund_len; struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("refund_amount", - &refund_amount), - GNUNET_JSON_spec_json ("refunds", - &refunds), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &merchant_pub), + TALER_JSON_spec_amount_any ( + "refund_amount", + &wrr.details.ok.refund_amount), + GNUNET_JSON_spec_array_const ( + "refunds", + &refunds), + GNUNET_JSON_spec_fixed_auto ( + "merchant_pub", + &wrr.details.ok.merchant_pub), GNUNET_JSON_spec_end () }; @@ -150,27 +122,21 @@ handle_refund_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - cb_failure (orh, - TALER_EC_GENERIC_REPLY_MALFORMED, - json); - TALER_MERCHANT_wallet_post_order_refund_cancel (orh); - return; + wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wrr.hr.http_status = 0; + break; } - - if (! json_is_array (refunds)) + refund_len = json_array_size (refunds); + if ( (json_array_size (refunds) != (size_t) refund_len) || + (refund_len > MAX_REFUNDS) ) { - GNUNET_break_op (0); - cb_failure (orh, - TALER_EC_GENERIC_REPLY_MALFORMED, - json); - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_wallet_post_order_refund_cancel (orh); - return; + GNUNET_break (0); + wrr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + wrr.hr.http_status = 0; + break; } - - refund_len = json_array_size (refunds); { - struct TALER_MERCHANT_RefundDetail rds[refund_len]; + struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (refund_len)]; memset (rds, 0, @@ -182,10 +148,26 @@ handle_refund_finished (void *cls, i); const char *refund_status_type; uint32_t exchange_status; - int ret; + uint32_t eec = 0; struct GNUNET_JSON_Specification espec[] = { + GNUNET_JSON_spec_string ("type", + &refund_status_type), GNUNET_JSON_spec_uint32 ("exchange_status", &exchange_status), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rd->rtransaction_id), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rd->coin_pub), + TALER_JSON_spec_amount_any ("refund_amount", + &rd->refund_amount), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("exchange_reply", + &rd->hr.reply), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("exchange_code", + &eec), + NULL), GNUNET_JSON_spec_end () }; @@ -195,151 +177,85 @@ handle_refund_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - cb_failure (orh, - TALER_EC_GENERIC_REPLY_MALFORMED, - json); - TALER_MERCHANT_wallet_post_order_refund_cancel (orh); - return; + wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wrr.hr.http_status = 0; + goto finish; } - if (MHD_HTTP_OK == exchange_status) + rd->hr.http_status = exchange_status; + rd->hr.ec = (enum TALER_ErrorCode) eec; + switch (exchange_status) { - struct GNUNET_JSON_Specification rspec[] = { - GNUNET_JSON_spec_string ("type", - &refund_status_type), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rd->exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rd->exchange_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &rd->rtransaction_id), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rd->coin_pub), - TALER_JSON_spec_amount_any ("refund_amount", - &rd->refund_amount), - GNUNET_JSON_spec_end () - }; - - ret = GNUNET_JSON_parse (jrefund, - rspec, - NULL, NULL); - if (GNUNET_OK == ret) + case MHD_HTTP_OK: { - /* check that type field is correct */ - if (0 != strcmp ("success", refund_status_type)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - } - } - } - else - { - struct GNUNET_JSON_Specification rspec[] = { - GNUNET_JSON_spec_string ("type", - &refund_status_type), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rd->coin_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &rd->rtransaction_id), - TALER_JSON_spec_amount_any ("refund_amount", - &rd->refund_amount), - GNUNET_JSON_spec_end () - }; - - ret = GNUNET_JSON_parse (jrefund, + struct GNUNET_JSON_Specification rspec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rd->details.ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rd->details.ok.exchange_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jrefund, rspec, - NULL, NULL); - if (GNUNET_OK == ret) - { - /* parse optional arguments */ - json_t *jec; - - jec = json_object_get (jrefund, - "exchange_code"); - if (NULL != jec) + NULL, + NULL)) { - if (! json_is_integer (jec)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - } - else - { - rd->hr.ec = (enum TALER_ErrorCode) json_integer_value (jec); - } + GNUNET_break_op (0); + wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wrr.hr.http_status = 0; + goto finish; } - rd->hr.reply = json_object_get (jrefund, - "exchange_reply"); /* check that type field is correct */ - if (0 != strcmp ("failure", refund_status_type)) + if (0 != strcmp ("success", + refund_status_type)) { GNUNET_break_op (0); - ret = GNUNET_SYSERR; + wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wrr.hr.http_status = 0; + goto finish; } } - } - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - cb_failure (orh, - TALER_EC_GENERIC_REPLY_MALFORMED, - json); - TALER_MERCHANT_wallet_post_order_refund_cancel (orh); - return; - } - rd->hr.http_status = exchange_status; - } - - { - struct TALER_MERCHANT_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; + break; /* end MHD_HTTP_OK */ + default: + if (0 != strcmp ("failure", + refund_status_type)) + { + GNUNET_break_op (0); + wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + wrr.hr.http_status = 0; + goto finish; + } + } /* switch on exchange status code */ + } /* for all refunds */ - orh->cb (orh->cb_cls, - &hr, - &refund_amount, - &merchant_pub, - rds, - refund_len); - } - } - GNUNET_JSON_parse_free (spec); - } + wrr.details.ok.refunds = rds; + wrr.details.ok.refunds_length = refund_len; + orh->cb (orh->cb_cls, + &wrr); + TALER_MERCHANT_wallet_post_order_refund_cancel (orh); + return; + } /* end 'rds' scope */ + } /* case MHD_HTTP_OK */ break; case MHD_HTTP_NO_CONTENT: - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL, - NULL, - 0); break; case MHD_HTTP_CONFLICT: case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL, - NULL, - 0); + wrr.hr.ec = TALER_JSON_get_error_code (json); + wrr.hr.hint = TALER_JSON_get_error_hint (json); break; default: GNUNET_break_op (0); /* unexpected status code */ TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); - orh->cb (orh->cb_cls, - &hr, - NULL, - NULL, - NULL, - 0); + &wrr.hr); break; } +finish: + orh->cb (orh->cb_cls, + &wrr); TALER_MERCHANT_wallet_post_order_refund_cancel (orh); } |