commit 5c7fbb8fe6fa87b0d74f118ff3f21b16ea45f194
parent 706748172cfe93b12f93c5dcdd33cc75a50b2db0
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Thu, 1 Jan 2026 01:18:51 +0100
basic stuff for templates + paivana template skeleton
Diffstat:
36 files changed, 5341 insertions(+), 747 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_get-templates-ID.c b/src/backend/taler-merchant-httpd_get-templates-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2022-2024 Taler Systems SA
+ (C) 2022-2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -20,9 +20,442 @@
*/
#include "platform.h"
#include "taler-merchant-httpd_get-templates-ID.h"
+#include "taler-merchant-httpd_helper.h"
#include <taler/taler_json_lib.h>
+/**
+ * Context for building inventory template payloads.
+ */
+struct InventoryPayloadContext
+{
+ /**
+ * Selected category IDs (as JSON array).
+ */
+ const json_t *selected_categories;
+
+ /**
+ * Selected product IDs (as JSON array) from contract_template.
+ */
+ const json_t *selected_products;
+
+ /**
+ * Whether all products are selected.
+ */
+ bool selected_all;
+
+ /**
+ * JSON array of products to build.
+ */
+ json_t *products;
+
+ /**
+ * JSON array of categories to build.
+ */
+ json_t *category_payload;
+
+ /**
+ * JSON array of units to build.
+ */
+ json_t *unit_payload;
+
+ /**
+ * Set of categories referenced by the products.
+ */
+ struct TMH_CategorySet category_set;
+
+ /**
+ * Set of unit identifiers referenced by the products.
+ */
+ struct TMH_UnitSet unit_set;
+};
+
+
+/**
+ * Release resources associated with an inventory payload context.
+ *
+ * @param ipc inventory payload context
+ */
+static void
+inventory_payload_cleanup (struct InventoryPayloadContext *ipc)
+{
+ if (NULL != ipc->products)
+ json_decref (ipc->products);
+ if (NULL != ipc->category_payload)
+ json_decref (ipc->category_payload);
+ if (NULL != ipc->unit_payload)
+ json_decref (ipc->unit_payload);
+ GNUNET_free (ipc->category_set.ids);
+ TMH_unit_set_clear (&ipc->unit_set);
+ ipc->products = NULL;
+ ipc->category_payload = NULL;
+ ipc->unit_payload = NULL;
+ ipc->category_set.ids = NULL;
+ ipc->category_set.len = 0;
+}
+
+
+/**
+ * Add inventory product to JSON payload.
+ *
+ * @param cls inventory payload context
+ * @param product_id product identifier
+ * @param pd product details
+ * @param num_categories number of categories
+ * @param categories category IDs
+ */
+static void
+add_inventory_product (void *cls,
+ const char *product_id,
+ const struct
+ TALER_MERCHANTDB_InventoryProductDetails *pd,
+ size_t num_categories,
+ const uint64_t *categories)
+{
+ struct InventoryPayloadContext *ipc = cls;
+ json_t *jcategories;
+ json_t *product;
+
+ jcategories = json_array ();
+ GNUNET_assert (NULL != jcategories);
+ for (size_t i = 0; i < num_categories; i++)
+ {
+ /* Adding per product category */
+ TMH_category_set_add (&ipc->category_set,
+ categories[i]);
+ GNUNET_assert (0 ==
+ json_array_append_new (jcategories,
+ json_integer (categories[i])));
+ }
+ GNUNET_assert (0 < pd->price_array_length);
+ product = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("product_id",
+ product_id),
+ GNUNET_JSON_pack_string ("product_name",
+ pd->product_name),
+ GNUNET_JSON_pack_string ("description",
+ pd->description),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ pd->description_i18n)),
+ GNUNET_JSON_pack_string ("unit",
+ pd->unit),
+ TALER_JSON_pack_amount_array ("unit_prices",
+ pd->price_array_length,
+ pd->price_array),
+ GNUNET_JSON_pack_bool ("unit_allow_fraction",
+ pd->allow_fractional_quantity),
+ GNUNET_JSON_pack_uint64 ("unit_precision_level",
+ pd->fractional_precision_level),
+ GNUNET_JSON_pack_array_steal ("categories",
+ jcategories),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("taxes",
+ pd->taxes)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("image_hash",
+ pd->image_hash)));
+
+ /* Adding per product unit */
+ TMH_unit_set_add (&ipc->unit_set,
+ pd->unit);
+
+ GNUNET_assert (0 ==
+ json_array_append_new (ipc->products,
+ product));
+}
+
+
+/**
+ * Add an inventory category to the payload if referenced.
+ *
+ * @param cls category payload context
+ * @param category_id category identifier
+ * @param category_name category name
+ * @param category_name_i18n translated names
+ * @param product_count number of products (unused)
+ */
+static void
+add_inventory_category (void *cls,
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t product_count)
+{
+ struct InventoryPayloadContext *ipc = cls;
+ json_t *category;
+
+ (void) product_count;
+ category = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("category_id",
+ category_id),
+ GNUNET_JSON_pack_string ("category_name",
+ category_name),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("category_name_i18n",
+ (json_t *) category_name_i18n)));
+ GNUNET_assert (0 ==
+ json_array_append_new (ipc->category_payload,
+ category));
+}
+
+
+/**
+ * Add an inventory unit to the payload if referenced and non-builtin.
+ *
+ * @param cls unit payload context
+ * @param unit_serial unit identifier
+ * @param ud unit details
+ */
+static void
+add_inventory_unit (void *cls,
+ uint64_t unit_serial,
+ const struct TALER_MERCHANTDB_UnitDetails *ud)
+{
+ struct InventoryPayloadContext *ipc = cls;
+ json_t *unit;
+
+ (void) unit_serial;
+
+ unit = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("unit",
+ ud->unit),
+ GNUNET_JSON_pack_string ("unit_name_long",
+ ud->unit_name_long),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("unit_name_long_i18n",
+ ud->unit_name_long_i18n)),
+ GNUNET_JSON_pack_string ("unit_name_short",
+ ud->unit_name_short),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("unit_name_short_i18n",
+ ud->unit_name_short_i18n)),
+ GNUNET_JSON_pack_bool ("unit_allow_fraction",
+ ud->unit_allow_fraction),
+ GNUNET_JSON_pack_uint64 ("unit_precision_level",
+ ud->unit_precision_level));
+ GNUNET_assert (0 ==
+ json_array_append_new (ipc->unit_payload,
+ unit));
+}
+
+
+/**
+ * Build wallet-facing payload for inventory templates.
+ *
+ * @param connection HTTP connection
+ * @param mi merchant instance
+ * @param tp template details
+ * @return MHD result
+ */
+static MHD_RESULT
+handle_get_templates_inventory (
+ struct MHD_Connection *connection,
+ const struct TMH_MerchantInstance *mi,
+ const struct TALER_MERCHANTDB_TemplateDetails *tp)
+{
+ struct InventoryPayloadContext ipc;
+ const char **product_ids = NULL;
+ uint64_t *category_ids = NULL;
+ size_t num_product_ids = 0;
+ size_t num_category_ids = 0;
+ json_t *inventory_payload;
+ json_t *template_contract;
+ enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+
+ memset (&ipc,
+ 0,
+ sizeof (ipc));
+ ipc.products = json_array ();
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_categories",
+ &ipc.selected_categories),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_products",
+ &ipc.selected_products),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("selected_all",
+ &ipc.selected_all),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *err_name;
+ unsigned int err_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (tp->template_contract,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid inventory template_contract for field %s\n",
+ err_name);
+ inventory_payload_cleanup (&ipc);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ err_name);
+ }
+ }
+
+ if (! ipc.selected_all)
+ {
+ // TODO: maybe, these 2 sections would be better to save to the db, in the post/patch part
+ // and not here, yet it is quite lightweight and straightforward so I wouldn't really care...
+ if (NULL != ipc.selected_products)
+ {
+ size_t max_ids;
+
+ max_ids = json_array_size (ipc.selected_products);
+ if (0 < max_ids)
+ product_ids = GNUNET_new_array (max_ids,
+ const char *);
+ for (size_t i = 0; i < max_ids; i++)
+ {
+ const json_t *entry = json_array_get (ipc.selected_products,
+ i);
+
+ if (json_is_string (entry))
+ product_ids[num_product_ids++] = json_string_value (entry);
+ }
+ }
+ if (NULL != ipc.selected_categories)
+ {
+ size_t max_categories;
+
+ max_categories = json_array_size (ipc.selected_categories);
+ if (0 < max_categories)
+ category_ids = GNUNET_new_array (max_categories,
+ uint64_t);
+ for (size_t i = 0; i < max_categories; i++)
+ {
+ const json_t *entry = json_array_get (ipc.selected_categories,
+ i);
+
+ if (json_is_integer (entry) &&
+ (0 <= json_integer_value (entry)))
+ category_ids[num_category_ids++]
+ = (uint64_t) json_integer_value (entry);
+ }
+ }
+ }
+
+ if (ipc.selected_all)
+ qs = TMH_db->lookup_inventory_products (TMH_db->cls,
+ mi->settings.id,
+ &add_inventory_product,
+ &ipc);
+ else if ( (0 < num_product_ids) ||
+ (0 < num_category_ids) )
+ {
+ qs = TMH_db->lookup_inventory_products_filtered (TMH_db->cls,
+ mi->settings.id,
+ product_ids,
+ num_product_ids,
+ category_ids,
+ num_category_ids,
+ &add_inventory_product,
+ &ipc);
+
+ GNUNET_free (product_ids);
+ GNUNET_free (category_ids);
+ }
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ inventory_payload_cleanup (&ipc);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_inventory_products");
+ }
+
+ ipc.category_payload = json_array ();
+ GNUNET_assert (NULL != ipc.category_payload);
+ if (0 < ipc.category_set.len)
+ {
+ qs = TMH_db->lookup_categories_by_ids (TMH_db->cls,
+ mi->settings.id,
+ ipc.category_set.ids,
+ ipc.category_set.len,
+ &add_inventory_category,
+ &ipc);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ inventory_payload_cleanup (&ipc);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_categories_by_ids");
+ }
+ }
+
+ ipc.unit_payload = json_array ();
+ GNUNET_assert (NULL != ipc.unit_payload);
+ if (0 < ipc.unit_set.len)
+ {
+ qs = TMH_db->lookup_custom_units_by_names (
+ TMH_db->cls,
+ mi->settings.id,
+ (const char *const *) ipc.unit_set.
+ units,
+ ipc.unit_set.len,
+ &add_inventory_unit,
+ &ipc);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ inventory_payload_cleanup (&ipc);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_custom_units_by_names");
+ }
+ }
+
+ inventory_payload = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("products",
+ ipc.products),
+ GNUNET_JSON_pack_array_steal ("categories",
+ ipc.category_payload),
+ GNUNET_JSON_pack_array_steal ("units",
+ ipc.unit_payload));
+ ipc.products = NULL;
+ ipc.category_payload = NULL;
+ ipc.unit_payload = NULL;
+
+ template_contract = json_deep_copy (tp->template_contract);
+ GNUNET_assert (NULL != template_contract);
+ json_object_set_new (template_contract,
+ "inventory_payload",
+ inventory_payload);
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("editable_defaults",
+ tp->editable_defaults)),
+ GNUNET_JSON_pack_object_steal ("template_contract",
+ template_contract));
+ inventory_payload_cleanup (&ipc);
+ return ret;
+ }
+}
+
+
MHD_RESULT
TMH_get_templates_ID (
const struct TMH_RequestHandler *rh,
@@ -58,14 +491,36 @@ TMH_get_templates_ID (
{
MHD_RESULT ret;
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("editable_defaults",
- tp.editable_defaults)),
- GNUNET_JSON_pack_object_incref ("template_contract",
- tp.template_contract));
+ switch (TMH_template_type_from_contract (tp.template_contract))
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ ret = handle_get_templates_inventory (connection,
+ mi,
+ &tp);
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ /* TODO: PAIVANA add paivana-specific payload elements.
+ (Yet, I think, everything will be in the contract json so we can just pack it) */
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("editable_defaults",
+ tp.editable_defaults)),
+ GNUNET_JSON_pack_object_incref ("template_contract",
+ tp.template_contract));
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ default:
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "template_type");
+ break;
+ }
TALER_MERCHANTDB_template_details_free (&tp);
return ret;
}
diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c
@@ -466,9 +466,44 @@ TMH_products_array_valid (const json_t *products)
}
+enum TALER_MERCHANT_TemplateType
+TMH_template_type_from_contract (const json_t *template_contract)
+{
+ const json_t *type_val;
+
+ if (NULL == template_contract)
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
+ type_val = json_object_get (template_contract,
+ "template_type");
+ if (! json_is_string (type_val))
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+ return TALER_MERCHANT_template_type_from_string (
+ json_string_value (type_val));
+}
+
+
+const char *
+TMH_template_type_to_string (enum TALER_MERCHANT_TemplateType template_type)
+{
+ switch (template_type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ return "fixed-order";
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ return "inventory-cart";
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ return "paivana";
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+ return NULL;
+}
+
+
bool
TMH_template_contract_valid (const json_t *template_contract)
{
+ enum TALER_MERCHANT_TemplateType template_type;
const char *summary;
const char *currency;
struct TALER_Amount amount = { .value = 0};
@@ -496,6 +531,134 @@ TMH_template_contract_valid (const json_t *template_contract)
const char *ename;
unsigned int eline;
+ template_type = TMH_template_type_from_contract (template_contract);
+ if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == template_type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid template_type\n");
+ return false;
+ }
+
+ if (TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART == template_type)
+ {
+ bool selected_all = false;
+ bool choose_one = false;
+ bool request_tip = false;
+ const json_t *selected_categories = NULL;
+ const json_t *selected_products = NULL;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("summary",
+ &summary),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("request_tip",
+ &request_tip),
+ NULL),
+ GNUNET_JSON_spec_relative_time ("pay_duration",
+ &pay_duration),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("selected_all",
+ &selected_all),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_categories",
+ &selected_categories),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_products",
+ &selected_products),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("choose_one",
+ &choose_one),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (template_contract,
+ ispec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid inventory template_contract for field %s\n",
+ ename);
+ return false;
+ }
+ if (NULL != selected_categories)
+ {
+ const json_t *entry;
+ size_t idx;
+
+ json_array_foreach ((json_t *) selected_categories, idx, entry)
+ {
+ if (! json_is_integer (entry))
+ {
+ GNUNET_break_op (0);
+ return false;
+ }
+ if (0 > json_integer_value (entry))
+ {
+ GNUNET_break_op (0);
+ return false;
+ }
+ }
+ }
+ if (NULL != selected_products)
+ {
+ const json_t *entry;
+ size_t idx;
+
+ json_array_foreach ((json_t *) selected_products, idx, entry)
+ {
+ if (! json_is_string (entry))
+ {
+ GNUNET_break_op (0);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ if (TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA == template_type)
+ {
+ const char *paivana_id;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_string ("paivana_id",
+ &paivana_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (template_contract,
+ spec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid paivana template_contract for field %s\n",
+ ename);
+ return false;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (template_contract,
+ pspec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid paivana template_contract for field %s\n",
+ ename);
+ return false;
+ }
+ (void) paivana_id;
+ /* TODO: PAIVANA validate paivana-specific fields beyond presence. */
+ return true;
+ }
+
if (GNUNET_OK !=
GNUNET_JSON_parse (template_contract,
spec,
@@ -511,6 +674,100 @@ TMH_template_contract_valid (const json_t *template_contract)
}
+bool
+TMH_category_set_contains (const struct TMH_CategorySet *set,
+ uint64_t id)
+{
+ for (size_t i = 0; i < set->len; i++)
+ if (set->ids[i] == id)
+ return true;
+ return false;
+}
+
+
+void
+TMH_category_set_add (struct TMH_CategorySet *set,
+ uint64_t id)
+{
+ if (TMH_category_set_contains (set,
+ id))
+ return;
+ GNUNET_array_append (set->ids,
+ set->len,
+ id);
+}
+
+
+bool
+TMH_unit_set_contains (const struct TMH_UnitSet *set,
+ const char *unit)
+{
+ for (unsigned int i = 0; i < set->len; i++)
+ if (0 == strcmp (set->units[i],
+ unit))
+ return true;
+ return false;
+}
+
+
+void
+TMH_unit_set_add (struct TMH_UnitSet *set,
+ const char *unit)
+{
+ if (TMH_unit_set_contains (set,
+ unit))
+ return;
+ GNUNET_array_append (set->units,
+ set->len,
+ GNUNET_strdup (unit));
+}
+
+
+void
+TMH_unit_set_clear (struct TMH_UnitSet *set)
+{
+ for (unsigned int i = 0; i < set->len; i++)
+ GNUNET_free (set->units[i]);
+ GNUNET_free (set->units);
+ set->units = NULL;
+ set->len = 0;
+}
+
+
+bool
+TMH_taxes_array_valid (const json_t *taxes)
+{
+ json_t *tax;
+ size_t idx;
+
+ if (! json_is_array (taxes))
+ return false;
+ json_array_foreach (taxes, idx, tax)
+ {
+ struct TALER_Amount amount;
+ const char *name;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ TALER_JSON_spec_amount_any ("tax",
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (NULL,
+ tax,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return false;
+ }
+ }
+ return true;
+}
+
+
struct TMH_WireMethod *
TMH_setup_wire_account (
struct TALER_FullPayto payto_uri,
diff --git a/src/backend/taler-merchant-httpd_helper.h b/src/backend/taler-merchant-httpd_helper.h
@@ -27,6 +27,7 @@
#include "taler-merchant-httpd.h"
+#include <taler/taler_merchant_util.h>
/**
* check @a accounts for well-formedness
@@ -65,6 +66,163 @@ TMH_products_array_valid (const json_t *products);
/**
+ * Parse decimal quantity expressed as string for request handling.
+ *
+ * @param value string to parse
+ * @param[out] integer_part result integer component
+ * @param[out] fractional_part result fractional component (0..MERCHANT_UNIT_FRAC_BASE-1)
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on validation failure
+ */
+enum GNUNET_GenericReturnValue
+TMH_parse_fractional_string (const char *value,
+ int64_t *integer_part,
+ uint32_t *fractional_part);
+
+/**
+ * Kind of fixed-decimal value the helpers operate on.
+ * Primarily distinguishes how special sentinel values (such as "-1"
+ * meaning infinity for stock) must be encoded.
+ */
+enum TMH_ValueKind
+{
+ TMH_VK_QUANTITY, /* -1 is illegal */
+ TMH_VK_STOCK /* -1 means "infinity" */
+};
+
+/**
+ * Determine template type from a template contract.
+ *
+ * @param template_contract contract JSON
+ * @return template type (defaults to fixed order)
+ */
+enum TALER_MERCHANT_TemplateType
+TMH_template_type_from_contract (const json_t *template_contract);
+
+/**
+ * Convert template type to its string representation.
+ *
+ * @param template_type template type to convert
+ * @return string name or NULL for invalid types
+ */
+const char *
+TMH_template_type_to_string (enum TALER_MERCHANT_TemplateType template_type);
+
+/**
+ * Set of category IDs.
+ */
+struct TMH_CategorySet
+{
+ /**
+ * Category IDs.
+ */
+ uint64_t *ids;
+
+ /**
+ * Number of entries in @e ids.
+ */
+ unsigned int len;
+};
+
+/**
+ * Set of unit identifiers.
+ */
+struct TMH_UnitSet
+{
+ /**
+ * Unit identifiers.
+ */
+ char **units;
+
+ /**
+ * Number of entries in @e units.
+ */
+ unsigned int len;
+};
+/**
+ * Check if a category set already contains a given ID.
+ *
+ * @param set category set
+ * @param id category id
+ * @return true if present
+ */
+bool
+TMH_category_set_contains (const struct TMH_CategorySet *set,
+ uint64_t id);
+
+/**
+ * Add a category ID to a set if not already present.
+ *
+ * @param set category set
+ * @param id category id
+ */
+void
+TMH_category_set_add (struct TMH_CategorySet *set,
+ uint64_t id);
+
+/**
+ * Check if a unit set already contains a given unit.
+ *
+ * @param set unit set
+ * @param unit unit identifier
+ * @return true if present
+ */
+bool
+TMH_unit_set_contains (const struct TMH_UnitSet *set,
+ const char *unit);
+
+/**
+ * Add a unit identifier to a set if not already present.
+ *
+ * @param set unit set
+ * @param unit unit identifier
+ */
+void
+TMH_unit_set_add (struct TMH_UnitSet *set,
+ const char *unit);
+
+/**
+ * Clear a unit set and free its contents.
+ *
+ * @param set unit set to clear
+ */
+void
+TMH_unit_set_clear (struct TMH_UnitSet *set);
+
+/**
+ * Extract a fixed-decimal number that may be supplied either
+ * - as pure integer (e.g. "total_stock"), or
+ * - as decimal text (e.g. "unit_total_stock").
+ *
+ * Rules:
+ * - If both forms are missing -> error.
+ * - If both are present -> they must match and the decimal must have no fraction.
+ * - For kind == TMH_VK_STOCK the integer value -1 represents infinity.
+ *
+ * @param kind See #TMH_ValueKind
+ * @param allow_fractional False: any fractional part is rejected
+ * @param int_missing True if client omitted the integer field
+ * @param int_raw Raw integer (undefined if @a int_missing is true)
+ * @param str_missing True if client omitted the string field
+ * @param str_raw Raw UTF-8 string (undefined if @a str_missing is true)
+ * @param[out] int_out Canonicalised integer part
+ * @param[out] frac_out Canonicalised fractional part
+ * @param[out] error_param Set to offending field name on failure
+ * @param int_field Integer field name (for error reporting)
+ * @param str_field String field name (for error reporting)
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TMH_process_quantity_inputs (enum TMH_ValueKind kind,
+ bool allow_fractional,
+ bool int_missing,
+ int64_t int_raw,
+ bool str_missing,
+ const char *str_raw,
+ uint64_t *int_out,
+ uint32_t *frac_out,
+ const char **error_param);
+
+/**
* Lookup the defaults for @a unit within @a mi and fall back to sane
* values (disallow fractional quantities, zero precision) if no data
* is available.
diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c
@@ -28,6 +28,7 @@
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_helper.h"
#include <taler/taler_json_lib.h>
+#include <taler/taler_merchant_util.h>
/**
@@ -71,18 +72,153 @@ cleanup_use_context (void *cls)
}
-MHD_RESULT
-TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+/**
+ * Check if a product ID appears in the selected_products list.
+ *
+ * @param selected_products JSON array of selected product IDs, may be NULL
+ * @param product_id product ID to check
+ * @return true if the product ID is selected
+ */
+static bool
+product_id_selected (const json_t *selected_products,
+ const char *product_id)
+{
+ const json_t *entry;
+ size_t idx;
+
+ if (NULL == selected_products)
+ return false;
+ json_array_foreach ((json_t *) selected_products, idx, entry)
+ {
+ if (! json_is_string (entry))
+ continue;
+ if (0 == strcmp (json_string_value (entry),
+ product_id))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Check if any product category is in the selected_categories list.
+ *
+ * @param selected_categories JSON array of selected category IDs, may be NULL
+ * @param num_categories length of @a categories
+ * @param categories list of category IDs for the product
+ * @return true if any category matches
+ */
+static bool
+categories_selected (const json_t *selected_categories,
+ size_t num_categories,
+ const uint64_t *categories)
+{
+ const json_t *entry;
+ size_t idx;
+
+ if (NULL == selected_categories)
+ return false;
+ json_array_foreach ((json_t *) selected_categories, idx, entry)
+ {
+ uint64_t selected_id;
+
+ if (! json_is_integer (entry))
+ continue;
+ if (0 > json_integer_value (entry))
+ continue;
+ selected_id = (uint64_t) json_integer_value (entry);
+ for (size_t i = 0; i < num_categories; i++)
+ {
+ if (categories[i] == selected_id)
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Compute the line total for a product based on quantity.
+ *
+ * @param unit_price price per unit
+ * @param quantity integer quantity
+ * @param quantity_frac fractional quantity (0..MERCHANT_UNIT_FRAC_BASE-1)
+ * @param[out] line_total resulting line total
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+compute_line_total (const struct TALER_Amount *unit_price,
+ uint64_t quantity,
+ uint32_t quantity_frac,
+ struct TALER_Amount *line_total)
+{
+ struct TALER_Amount tmp;
+
+ if (quantity > UINT32_MAX)
+ return GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ TALER_amount_set_zero (unit_price->currency,
+ line_total))
+ return GNUNET_SYSERR;
+ if (0 != quantity)
+ {
+ if (0 >
+ TALER_amount_multiply (line_total,
+ unit_price,
+ (uint32_t) quantity))
+ return GNUNET_SYSERR;
+ }
+ if (0 != quantity_frac)
+ {
+ if (0 >
+ TALER_amount_multiply (&tmp,
+ unit_price,
+ quantity_frac))
+ return GNUNET_SYSERR;
+ TALER_amount_divide (&tmp,
+ &tmp,
+ MERCHANT_UNIT_FRAC_BASE);
+ if (0 >
+ TALER_amount_add (line_total,
+ line_total,
+ &tmp))
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle POST /templates/$ID for inventory templates.
+ *
+ * @param connection connection to reply on
+ * @param hc handler context
+ * @param uc use context
+ * @param summary summary override (optional)
+ * @param fulfillment_url fulfillment URL (optional)
+ * @param fulfillment_message fulfillment message (optional)
+ * @return MHD result
+ */
+static MHD_RESULT
+handle_using_templates_inventory (struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc,
+ struct UseContext *uc,
+ const char *summary,
+ const char *fulfillment_url,
+ const char *fulfillment_message)
{
struct TMH_MerchantInstance *mi = hc->instance;
- const char *template_id = hc->infix;
- const char *summary = NULL;
- const char *fulfillment_url = NULL;
- const char *fulfillment_message = NULL;
+ const json_t *inventory_selection = NULL;
+ const json_t *selected_categories = NULL;
+ const json_t *selected_products = NULL;
+ const char *tsummary = NULL;
struct TALER_Amount amount;
+ struct TALER_Amount tip;
bool no_amount;
+ bool no_tip;
+ bool choose_one = false;
+ bool request_tip = false;
+ bool selected_all = false;
bool no_summary;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
@@ -93,75 +229,449 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
TALER_JSON_spec_amount_any ("amount",
&amount),
&no_amount),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("tip",
+ &tip),
+ &no_tip),
+ GNUNET_JSON_spec_array_const ("inventory_selection",
+ &inventory_selection),
GNUNET_JSON_spec_end ()
};
- struct UseContext *uc = hc->ctx;
+ enum GNUNET_GenericReturnValue res;
+ json_t *inventory_products;
+ json_t *tip_products = NULL;
+ struct TALER_Amount total;
+ bool total_init = false;
- if (NULL == uc)
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
{
- uc = GNUNET_new (struct UseContext);
- hc->ctx = uc;
- hc->cc = &cleanup_use_context;
- uc->ihc.instance = hc->instance;
+ GNUNET_break_op (0);
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
}
+ if (no_amount)
{
- enum GNUNET_GenericReturnValue res;
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "amount");
+ }
+ if ( (NULL == inventory_selection) ||
+ (! json_is_array (inventory_selection)) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_selection");
+ }
- res = TALER_MHD_parse_json_data (connection,
- hc->request_body,
- spec);
+ {
+ const json_t *tsummary_val;
+ const json_t *selected_categories_val;
+ const json_t *selected_products_val;
+ const json_t *choose_one_val;
+ const json_t *request_tip_val;
+ const json_t *selected_all_val;
+
+ tsummary_val = json_object_get (uc->etp.template_contract,
+ "summary");
+ if (json_is_string (tsummary_val))
+ tsummary = json_string_value (tsummary_val);
+ selected_categories_val = json_object_get (uc->etp.template_contract,
+ "selected_categories");
+ if (json_is_array (selected_categories_val))
+ selected_categories = selected_categories_val;
+ selected_products_val = json_object_get (uc->etp.template_contract,
+ "selected_products");
+ if (json_is_array (selected_products_val))
+ selected_products = selected_products_val;
+ choose_one_val = json_object_get (uc->etp.template_contract,
+ "choose_one");
+ if (json_is_boolean (choose_one_val))
+ choose_one = json_boolean_value (choose_one_val);
+ request_tip_val = json_object_get (uc->etp.template_contract,
+ "request_tip");
+ if (json_is_boolean (request_tip_val))
+ request_tip = json_boolean_value (request_tip_val);
+ selected_all_val = json_object_get (uc->etp.template_contract,
+ "selected_all");
+ if (json_is_boolean (selected_all_val))
+ selected_all = json_boolean_value (selected_all_val);
+ }
+
+ if ( (NULL != summary) &&
+ (NULL != tsummary) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
+ NULL);
+ }
+ if ( (NULL == summary) &&
+ (NULL == tsummary) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
+ NULL);
+ }
+ no_summary = (NULL == summary);
+
+ if (! no_tip && ! request_tip)
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "tip");
+ }
+
+ if (choose_one &&
+ (1 != json_array_size (inventory_selection)))
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_selection");
+ }
+
+ inventory_products = json_array ();
+ GNUNET_assert (NULL != inventory_products);
+ for (size_t i = 0; i < json_array_size (inventory_selection); i++)
+ {
+ const char *product_id;
+ const char *quantity;
+ struct TALER_MERCHANTDB_ProductDetails pd;
+ enum GNUNET_DB_QueryStatus qs;
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("product_id",
+ &product_id),
+ GNUNET_JSON_spec_string ("quantity",
+ &quantity),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *err_name;
+ unsigned int err_line;
+ struct TALER_Amount line_total;
+ uint64_t quantity_value = 0;
+ uint32_t quantity_frac = 0;
+ const char *eparam = NULL;
+
+ res = GNUNET_JSON_parse (json_array_get (inventory_selection,
+ i),
+ ispec,
+ &err_name,
+ &err_line);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
+ GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_selection");
}
- }
- if (! uc->have_etp)
- {
- enum GNUNET_DB_QueryStatus qs;
+ qs = TMH_db->lookup_product (TMH_db->cls,
+ mi->settings.id,
+ product_id,
+ &pd,
+ &num_categories,
+ &categories);
+ if (qs <= 0)
+ {
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ unsigned int http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- qs = TMH_db->lookup_template (TMH_db->cls,
- mi->settings.id,
- template_id,
- &uc->etp);
- switch (qs)
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ http_status = MHD_HTTP_NOT_FOUND;
+ ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_assert (0);
+ }
+ GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ return TALER_MHD_reply_with_error (connection,
+ http_status,
+ ec,
+ product_id);
+ }
+
+ if (! selected_all)
+ {
+ bool allowed = false;
+
+ if (product_id_selected (selected_products,
+ product_id))
+ allowed = true;
+ else if (categories_selected (selected_categories,
+ num_categories,
+ categories))
+ allowed = true;
+
+ if (! allowed)
+ {
+ GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ GNUNET_free (categories);
+ TALER_MERCHANTDB_product_details_free (&pd);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_selection");
+ }
+ }
+
+ if (GNUNET_OK !=
+ TMH_process_quantity_inputs (TMH_VK_QUANTITY,
+ pd.allow_fractional_quantity,
+ true,
+ 0,
+ false,
+ quantity,
+ &quantity_value,
+ &quantity_frac,
+ &eparam))
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- /* Clean up and fail hard */
- GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ GNUNET_free (categories);
+ TALER_MERCHANTDB_product_details_free (&pd);
return TALER_MHD_reply_with_error (
connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* this should be impossible (single select) */
- GNUNET_break (0);
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ eparam);
+ }
+
+ if (GNUNET_OK !=
+ compute_line_total (&pd.price,
+ quantity_value,
+ quantity_frac,
+ &line_total))
+ {
GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ GNUNET_free (categories);
+ TALER_MERCHANTDB_product_details_free (&pd);
return TALER_MHD_reply_with_error (
connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* template not found! */
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "quantity");
+ }
+
+ if (! total_init)
+ {
+ total = line_total;
+ total_init = true;
+ }
+ else
+ {
+ if (0 >
+ TALER_amount_add (&total,
+ &total,
+ &line_total))
+ {
+ GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ GNUNET_free (categories);
+ TALER_MERCHANTDB_product_details_free (&pd);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ line_total.currency);
+ }
+ }
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ inventory_products,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("product_id",
+ product_id),
+ GNUNET_JSON_pack_string ("unit_quantity",
+ quantity))));
+ GNUNET_free (categories);
+ TALER_MERCHANTDB_product_details_free (&pd);
+ }
+
+ if (! total_init)
+ {
+ GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "inventory_selection");
+ }
+
+ if (! no_tip)
+ {
+ if (0 >
+ TALER_amount_add (&total,
+ &total,
+ &tip))
+ {
GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
return TALER_MHD_reply_with_error (
connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
- template_id);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* all good */
- uc->have_etp = true;
- break;
- } /* End of the switch */
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ tip.currency);
+ }
+ tip_products = json_array ();
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ tip_products,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("description",
+ "tip"),
+ GNUNET_JSON_pack_uint64 ("quantity",
+ 1),
+ TALER_JSON_pack_amount ("price",
+ &tip))));
+ }
+
+ if (0 != TALER_amount_cmp (&amount,
+ &total))
+ {
+ GNUNET_JSON_parse_free (spec);
+ json_decref (inventory_products);
+ json_decref (tip_products);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
+ NULL);
+ }
+
+ {
+ json_t *body;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_id",
+ uc->etp.otp_id)),
+ GNUNET_JSON_pack_array_steal ("inventory_products",
+ inventory_products),
+ GNUNET_JSON_pack_object_steal (
+ "order",
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &total),
+ GNUNET_JSON_pack_string ("summary",
+ no_summary
+ ? tsummary
+ : summary),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ fulfillment_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_message",
+ fulfillment_message)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("products",
+ tip_products)))));
+ GNUNET_assert (NULL != body);
+ uc->ihc.request_body = body;
}
+ GNUNET_JSON_parse_free (spec);
+ return TMH_private_post_orders (
+ NULL, /* not even used */
+ connection,
+ &uc->ihc);
+}
+
+
+/**
+ * Handle POST /templates/$ID for fixed-order templates.
+ *
+ * @param connection connection to reply on
+ * @param hc handler context
+ * @param uc use context
+ * @param summary summary override (optional)
+ * @param fulfillment_url fulfillment URL (optional)
+ * @param fulfillment_message fulfillment message (optional)
+ * @return MHD result
+ */
+static MHD_RESULT
+handle_using_templates_fixed (struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc,
+ struct UseContext *uc,
+ const char *summary,
+ const char *fulfillment_url,
+ const char *fulfillment_message)
+{
+ struct TALER_Amount amount;
+ struct TALER_Amount tip;
+ bool no_amount;
+ bool no_tip;
+ bool no_summary;
+ 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),
+ &no_amount),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("tip",
+ &tip),
+ &no_tip),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ json_t *tip_products = NULL;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+
if (NULL == uc->ihc.request_body)
{
/* template */
@@ -190,10 +700,8 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
&pay_duration),
GNUNET_JSON_spec_end ()
};
- json_t *fake_body;
{
- enum GNUNET_GenericReturnValue res;
const char *err_name;
unsigned int err_line;
@@ -250,6 +758,24 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
NULL);
}
+ if (! no_tip)
+ {
+ const struct TALER_Amount *total_amount;
+
+ total_amount = no_amount ? &tamount : &amount;
+ if (0 != TALER_amount_cmp_currency (&tip,
+ total_amount))
+ {
+ GNUNET_JSON_parse_free (spec);
+ json_decref (tip_products);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ tip.currency);
+ }
+ }
+
if ( (NULL != summary) &&
(NULL != tsummary) )
{
@@ -272,32 +798,50 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
NULL);
}
no_summary = (NULL == summary);
- fake_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("otp_id",
- uc->etp.otp_id)),
- GNUNET_JSON_pack_object_steal (
- "order",
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- no_amount ?
- &tamount :
- &amount),
- GNUNET_JSON_pack_string ("summary",
- no_summary ?
- tsummary :
- summary),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "fulfillment_url",
- fulfillment_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "fulfillment_message",
- fulfillment_message))
- ))
- );
- uc->ihc.request_body = fake_body;
+
+ {
+ json_t *body;
+ if (! no_tip)
+ {
+ tip_products = json_array ();
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ tip_products,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("description",
+ "tip"),
+ GNUNET_JSON_pack_uint64 ("quantity",
+ 1),
+ TALER_JSON_pack_amount ("price",
+ &tip))));
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_id",
+ uc->etp.otp_id)),
+ GNUNET_JSON_pack_object_steal (
+ "order",
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ no_amount
+ ? &tamount
+ : &amount),
+ GNUNET_JSON_pack_string ("summary",
+ no_summary
+ ? tsummary
+ : summary),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ fulfillment_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_message",
+ fulfillment_message)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("products",
+ tip_products)))));
+ GNUNET_assert (NULL != body);
+ uc->ihc.request_body = body;
+ }
}
return TMH_private_post_orders (
@@ -305,3 +849,126 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
connection,
&uc->ihc);
}
+
+
+/**
+ * Handle POST /templates/$ID for paivana templates.
+ *
+ * @param connection connection to reply on
+ * @param hc handler context
+ * @param uc use context
+ * @param summary summary override (optional)
+ * @param fulfillment_url fulfillment URL (optional)
+ * @param fulfillment_message fulfillment message (optional)
+ * @return MHD result
+ */
+static MHD_RESULT
+handle_using_templates_paivana (struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc,
+ struct UseContext *uc,
+ const char *summary,
+ const char *fulfillment_url,
+ const char *fulfillment_message)
+{
+ /* TODO: PAIVANA include paivana_id in instantiation flow. */
+ return handle_using_templates_fixed (connection,
+ hc,
+ uc,
+ summary,
+ fulfillment_url,
+ fulfillment_message);
+}
+
+
+MHD_RESULT
+TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *template_id = hc->infix;
+ const char *summary = NULL;
+ const char *fulfillment_url = NULL;
+ const char *fulfillment_message = NULL;
+ struct UseContext *uc = hc->ctx;
+
+ if (NULL == uc)
+ {
+ uc = GNUNET_new (struct UseContext);
+ hc->ctx = uc;
+ hc->cc = &cleanup_use_context;
+ uc->ihc.instance = hc->instance;
+ }
+
+ if (! uc->have_etp)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_template (TMH_db->cls,
+ mi->settings.id,
+ template_id,
+ &uc->etp);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ /* Clean up and fail hard */
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* this should be impossible (single select) */
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* template not found! */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
+ template_id);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* all good */
+ uc->have_etp = true;
+ break;
+ } /* End of the switch */
+ }
+ switch (TMH_template_type_from_contract (uc->etp.template_contract))
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ return handle_using_templates_fixed (connection,
+ hc,
+ uc,
+ summary,
+ fulfillment_url,
+ fulfillment_message);
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ /* TODO: PAIVANA handle paivana-specific instantiation. */
+ return handle_using_templates_paivana (connection,
+ hc,
+ uc,
+ summary,
+ fulfillment_url,
+ fulfillment_message);
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ return handle_using_templates_inventory (connection,
+ hc,
+ uc,
+ summary,
+ fulfillment_url,
+ fulfillment_message);
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "template_contract");
+}
diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c
@@ -87,6 +87,28 @@ determine_cause (struct MHD_Connection *connection,
/**
+ * Validate template contract based on its type.
+ *
+ * @param template_contract JSON template contract
+ * @return true if valid
+ */
+static bool
+validate_template_contract_by_type (const json_t *template_contract)
+{
+ switch (TMH_template_type_from_contract (template_contract))
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ return TMH_template_contract_valid (template_contract);
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+ return false;
+}
+
+
+/**
* PATCH configuration of an existing instance, given its configuration.
*
* @param rh context of the handler
@@ -133,7 +155,7 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh,
: MHD_NO;
}
- if (! TMH_template_contract_valid (tp.template_contract))
+ if (! validate_template_contract_by_type (tp.template_contract))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
diff --git a/src/backend/taler-merchant-httpd_private-post-templates.c b/src/backend/taler-merchant-httpd_private-post-templates.c
@@ -58,6 +58,30 @@ templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *t1,
}
+/**
+ * Validate template contract based on its type.
+ *
+ * @param template_contract JSON template contract
+ * @return true if valid
+ */
+static bool
+validate_template_contract_by_type (const json_t *template_contract)
+{
+ /* TODO: I still think helper in main can be divided,
+ plus we might want to maintain some flexibility */
+ switch (TMH_template_type_from_contract (template_contract))
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ return TMH_template_contract_valid (template_contract);
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+ return false;
+}
+
+
MHD_RESULT
TMH_private_post_templates (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
@@ -101,7 +125,7 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh,
: MHD_NO;
}
}
- if (! TMH_template_contract_valid (tp.template_contract))
+ if (! validate_template_contract_by_type (tp.template_contract))
{
GNUNET_break_op (0);
json_dumpf (tp.template_contract,
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
@@ -144,6 +144,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_insert_webhook.h pg_insert_webhook.c \
pg_delete_unit.h pg_delete_unit.c \
pg_lookup_units.h pg_lookup_units.c \
+ pg_lookup_custom_units_by_names.h pg_lookup_custom_units_by_names.c \
pg_select_unit.h pg_select_unit.c \
pg_lookup_mfa_challenge.h pg_lookup_mfa_challenge.c \
pg_solve_mfa_challenge.h pg_solve_mfa_challenge.c \
@@ -171,6 +172,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_lookup_account.h pg_lookup_account.c \
pg_lookup_all_products.h pg_lookup_all_products.c \
pg_lookup_categories.h pg_lookup_categories.c \
+ pg_lookup_categories_by_ids.h pg_lookup_categories_by_ids.c \
pg_lookup_contract_terms.h pg_lookup_contract_terms.c \
pg_lookup_contract_terms2.h pg_lookup_contract_terms2.c \
pg_lookup_contract_terms3.h pg_lookup_contract_terms3.c \
@@ -180,6 +182,8 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_lookup_expected_transfers.h pg_lookup_expected_transfers.c \
pg_lookup_instance_auth.h pg_lookup_instance_auth.c \
pg_lookup_instances.h pg_lookup_instances.c \
+ pg_lookup_inventory_products.h pg_lookup_inventory_products.c \
+ pg_lookup_inventory_products_filtered.h pg_lookup_inventory_products_filtered.c \
pg_lookup_login_tokens.h pg_lookup_login_tokens.c \
pg_lookup_order.h pg_lookup_order.c \
pg_lookup_order_by_fulfillment.h pg_lookup_order_by_fulfillment.c \
diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c
@@ -36,6 +36,7 @@ TALER_MERCHANTDB_product_details_free (
json_decref (pd->taxes);
pd->taxes = NULL;
GNUNET_free (pd->image);
+ GNUNET_free (pd->image_hash);
json_decref (pd->address);
pd->address = NULL;
GNUNET_free (pd->price_array);
diff --git a/src/backenddb/pg_insert_template.c b/src/backenddb/pg_insert_template.c
@@ -47,6 +47,7 @@ TMH_PG_insert_template (void *cls,
: TALER_PQ_query_param_json (td->editable_defaults),
GNUNET_PQ_query_param_end
};
+ enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
@@ -63,7 +64,9 @@ TMH_PG_insert_template (void *cls,
" $2, $3, $4, $5::TEXT::JSONB, $6::TEXT::JSONB"
" FROM merchant_instances"
" WHERE merchant_id=$1");
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_template",
- params);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_template",
+ params);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
}
diff --git a/src/backenddb/pg_lookup_all_products.c b/src/backenddb/pg_lookup_all_products.c
@@ -146,6 +146,7 @@ lookup_products_cb (void *cls,
plc->extract_failed = true;
return;
}
+ pd.image_hash = NULL;
plc->cb (plc->cb_cls,
product_serial,
product_id,
diff --git a/src/backenddb/pg_lookup_categories_by_ids.c b/src/backenddb/pg_lookup_categories_by_ids.c
@@ -0,0 +1,145 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_categories_by_ids.c
+ * @brief Lookup product categories by ID
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_categories_by_ids.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_categories_by_ids().
+ */
+struct LookupCategoryContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_CategoriesCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+static void
+lookup_categories_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupCategoryContext *tlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t category_id;
+ char *category_name;
+ json_t *category_name_i18n;
+ uint64_t product_count;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("category_serial",
+ &category_id),
+ GNUNET_PQ_result_spec_string ("category_name",
+ &category_name),
+ TALER_PQ_result_spec_json ("category_name_i18n",
+ &category_name_i18n),
+ GNUNET_PQ_result_spec_uint64 ("product_count",
+ &product_count),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ tlc->extract_failed = true;
+ return;
+ }
+ tlc->cb (tlc->cb_cls,
+ category_id,
+ category_name,
+ category_name_i18n,
+ product_count);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_categories_by_ids (void *cls,
+ const char *instance_id,
+ const uint64_t *category_ids,
+ size_t num_category_ids,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupCategoryContext tlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_categories_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_array_uint64 (num_category_ids,
+ category_ids,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_categories_by_ids",
+ "SELECT"
+ " mc.category_serial"
+ ",mc.category_name"
+ ",mc.category_name_i18n::TEXT"
+ ",COALESCE(COUNT(mpc.product_serial),0)"
+ " AS product_count"
+ " FROM merchant_categories mc"
+ " LEFT JOIN merchant_product_categories mpc"
+ " USING (category_serial)"
+ " JOIN merchant_instances mi"
+ " USING (merchant_serial)"
+ " WHERE mi.merchant_id=$1"
+ " AND mc.category_serial = ANY ($2)"
+ " GROUP BY mc.category_serial"
+ " ORDER BY mc.category_serial;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_categories_by_ids",
+ params,
+ &lookup_categories_cb,
+ &tlc);
+ if (tlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_categories_by_ids.h b/src/backenddb/pg_lookup_categories_by_ids.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_categories_by_ids.h
+ * @brief Lookup product categories by ID
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_LOOKUP_CATEGORIES_BY_IDS_H
+#define PG_LOOKUP_CATEGORIES_BY_IDS_H
+
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup product categories by ID.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup categories for
+ * @param category_ids array of category IDs
+ * @param num_category_ids length of @a category_ids
+ * @param cb function to call on all categories found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_categories_by_ids (void *cls,
+ const char *instance_id,
+ const uint64_t *category_ids,
+ size_t num_category_ids,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_custom_units_by_names.c b/src/backenddb/pg_lookup_custom_units_by_names.c
@@ -0,0 +1,142 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_custom_units_by_names.c
+ * @brief Lookup custom measurement units by name
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_custom_units_by_names.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_custom_units_by_names().
+ */
+struct LookupUnitsContext
+{
+ TALER_MERCHANTDB_UnitsCallback cb;
+ void *cb_cls;
+ bool extract_failed;
+};
+
+
+static void
+lookup_units_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupUnitsContext *luc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_MERCHANTDB_UnitDetails ud = { 0 };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("unit_serial",
+ &ud.unit_serial),
+ GNUNET_PQ_result_spec_string ("unit",
+ &ud.unit),
+ GNUNET_PQ_result_spec_string ("unit_name_long",
+ &ud.unit_name_long),
+ GNUNET_PQ_result_spec_string ("unit_name_short",
+ &ud.unit_name_short),
+ TALER_PQ_result_spec_json ("unit_name_long_i18n",
+ &ud.unit_name_long_i18n),
+ TALER_PQ_result_spec_json ("unit_name_short_i18n",
+ &ud.unit_name_short_i18n),
+ GNUNET_PQ_result_spec_bool ("unit_allow_fraction",
+ &ud.unit_allow_fraction),
+ GNUNET_PQ_result_spec_uint32 ("unit_precision_level",
+ &ud.unit_precision_level),
+ GNUNET_PQ_result_spec_bool ("unit_active",
+ &ud.unit_active),
+ GNUNET_PQ_result_spec_bool ("unit_builtin",
+ &ud.unit_builtin),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ luc->extract_failed = true;
+ return;
+ }
+ luc->cb (luc->cb_cls,
+ ud.unit_serial,
+ &ud);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_custom_units_by_names (void *cls,
+ const char *instance_id,
+ const char *const *units,
+ size_t num_units,
+ TALER_MERCHANTDB_UnitsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupUnitsContext luc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_array_ptrs_string (num_units,
+ (const char **) units,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_custom_units_by_names",
+ "WITH mi AS ("
+ " SELECT merchant_serial FROM merchant_instances WHERE merchant_id=$1"
+ ")"
+ "SELECT cu.unit_serial"
+ " ,cu.unit"
+ " ,cu.unit_name_long"
+ " ,cu.unit_name_short"
+ " ,cu.unit_name_long_i18n"
+ " ,cu.unit_name_short_i18n"
+ " ,cu.unit_allow_fraction"
+ " ,cu.unit_precision_level"
+ " ,cu.unit_active"
+ " ,FALSE AS unit_builtin"
+ " FROM merchant_custom_units cu"
+ " JOIN mi ON cu.merchant_serial = mi.merchant_serial"
+ " WHERE cu.unit = ANY ($2)"
+ " ORDER BY cu.unit");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_custom_units_by_names",
+ params,
+ &lookup_units_cb,
+ &luc);
+ if (luc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_custom_units_by_names.h b/src/backenddb/pg_lookup_custom_units_by_names.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_custom_units_by_names.h
+ * @brief Lookup custom measurement units by name
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_LOOKUP_CUSTOM_UNITS_BY_NAMES_H
+#define PG_LOOKUP_CUSTOM_UNITS_BY_NAMES_H
+
+#include <taler/taler_util.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup custom measurement units by name.
+ *
+ * @param cls closure
+ * @param instance_id instance to fetch units for
+ * @param units array of unit identifiers
+ * @param num_units length of @a units
+ * @param cb function to call with each unit
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_custom_units_by_names (void *cls,
+ const char *instance_id,
+ const char *const *units,
+ size_t num_units,
+ TALER_MERCHANTDB_UnitsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_inventory_products.c b/src/backenddb/pg_lookup_inventory_products.c
@@ -0,0 +1,188 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_inventory_products.c
+ * @brief Implementation of the lookup_inventory_products function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_inventory_products.h"
+#include "pg_helper.h"
+
+/**
+ * Context used for TMH_PG_lookup_inventory_products().
+ */
+struct LookupInventoryProductsContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_InventoryProductCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Postgres context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about products.
+ *
+ * @param[in,out] cls of type `struct LookupInventoryProductsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_inventory_products_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupInventoryProductsContext *plc = cls;
+ struct PostgresClosure *pg = plc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *product_id;
+ struct TALER_MERCHANTDB_InventoryProductDetails pd;
+ size_t num_categories;
+ uint64_t *categories;
+ bool no_image_hash;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("product_id",
+ &product_id),
+ GNUNET_PQ_result_spec_string ("product_name",
+ &pd.product_name),
+ GNUNET_PQ_result_spec_string ("description",
+ &pd.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &pd.description_i18n),
+ GNUNET_PQ_result_spec_string ("unit",
+ &pd.unit),
+ TALER_PQ_result_spec_amount_with_currency ("price",
+ &pd.price),
+ TALER_PQ_result_spec_array_amount_with_currency (pg->conn,
+ "price_array",
+ &pd.price_array_length,
+ &pd.price_array),
+ TALER_PQ_result_spec_json ("taxes",
+ &pd.taxes),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("image_hash",
+ &pd.image_hash),
+ &no_image_hash),
+ GNUNET_PQ_result_spec_bool ("allow_fractional_quantity",
+ &pd.allow_fractional_quantity),
+ GNUNET_PQ_result_spec_uint32 ("fractional_precision_level",
+ &pd.fractional_precision_level),
+ GNUNET_PQ_result_spec_array_uint64 (pg->conn,
+ "categories",
+ &num_categories,
+ &categories),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ plc->extract_failed = true;
+ return;
+ }
+ plc->cb (plc->cb_cls,
+ product_id,
+ &pd,
+ num_categories,
+ categories);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_inventory_products (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_InventoryProductCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupInventoryProductsContext plc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ /* Can be overwritten by the lookup_inventory_products_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_inventory_products",
+ "SELECT"
+ " description"
+ ",description_i18n::TEXT"
+ ",product_name"
+ ",unit"
+ ",price"
+ ",price_array"
+ ",taxes::TEXT"
+ ",image_hash"
+ ",allow_fractional_quantity"
+ ",fractional_precision_level"
+ ",product_id"
+ ",t.category_array AS categories"
+ " FROM merchant_inventory minv"
+ " JOIN merchant_instances inst"
+ " USING (merchant_serial)"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = minv.product_serial"
+ " ) AS category_array"
+ " ) t"
+ " WHERE inst.merchant_id=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "lookup_inventory_products",
+ params,
+ &lookup_inventory_products_cb,
+ &plc);
+ /* If there was an error inside lookup_inventory_products_cb, return a hard error. */
+ if (plc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_inventory_products.h b/src/backenddb/pg_lookup_inventory_products.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_inventory_products.h
+ * @brief Lookup inventory product details for templates
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_LOOKUP_INVENTORY_PRODUCTS_H
+#define PG_LOOKUP_INVENTORY_PRODUCTS_H
+
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup inventory details for all products of an instance.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_inventory_products (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_InventoryProductCallback cb,
+ void *cb_cls);
+
+#endif /* PG_LOOKUP_INVENTORY_PRODUCTS_H */
diff --git a/src/backenddb/pg_lookup_inventory_products_filtered.c b/src/backenddb/pg_lookup_inventory_products_filtered.c
@@ -0,0 +1,210 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_inventory_products_filtered.c
+ * @brief Lookup inventory product details for templates (filtered)
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_inventory_products_filtered.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_inventory_products_filtered().
+ */
+struct LookupInventoryProductsContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_InventoryProductCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Postgres context for callbacks.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+static void
+lookup_inventory_products_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupInventoryProductsContext *plc = cls;
+ struct PostgresClosure *pg = plc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *product_id;
+ struct TALER_MERCHANTDB_InventoryProductDetails pd;
+ size_t num_categories;
+ uint64_t *categories;
+ bool no_image_hash;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("product_id",
+ &product_id),
+ GNUNET_PQ_result_spec_string ("product_name",
+ &pd.product_name),
+ GNUNET_PQ_result_spec_string ("description",
+ &pd.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &pd.description_i18n),
+ GNUNET_PQ_result_spec_string ("unit",
+ &pd.unit),
+ TALER_PQ_result_spec_amount_with_currency ("price",
+ &pd.price),
+ TALER_PQ_result_spec_array_amount_with_currency (pg->conn,
+ "price_array",
+ &pd.price_array_length,
+ &pd.price_array),
+ TALER_PQ_result_spec_json ("taxes",
+ &pd.taxes),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("image_hash",
+ &pd.image_hash),
+ &no_image_hash),
+ GNUNET_PQ_result_spec_bool ("allow_fractional_quantity",
+ &pd.allow_fractional_quantity),
+ GNUNET_PQ_result_spec_uint32 ("fractional_precision_level",
+ &pd.fractional_precision_level),
+ GNUNET_PQ_result_spec_array_uint64 (pg->conn,
+ "categories",
+ &num_categories,
+ &categories),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ plc->extract_failed = true;
+ return;
+ }
+ plc->cb (plc->cb_cls,
+ product_id,
+ &pd,
+ num_categories,
+ categories);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_inventory_products_filtered (
+ void *cls,
+ const char *instance_id,
+ const char *const *product_ids,
+ size_t num_product_ids,
+ const uint64_t *categories,
+ size_t num_categories,
+ TALER_MERCHANTDB_InventoryProductCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupInventoryProductsContext plc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ /* Can be overwritten by the lookup_inventory_products_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ (0 == num_product_ids)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_array_ptrs_string (
+ num_product_ids,
+ (const char **) product_ids,
+ pg->conn),
+ (0 == num_categories)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_array_uint64 (num_categories,
+ categories,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_inventory_products_filtered",
+ "SELECT"
+ " description"
+ ",description_i18n::TEXT"
+ ",product_name"
+ ",unit"
+ ",price"
+ ",price_array"
+ ",taxes::TEXT"
+ ",image_hash"
+ ",allow_fractional_quantity"
+ ",fractional_precision_level"
+ ",product_id"
+ ",t.category_array AS categories"
+ " FROM merchant_inventory minv"
+ " JOIN merchant_instances inst"
+ " USING (merchant_serial)"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = minv.product_serial"
+ " ) AS category_array"
+ " ) t"
+ " WHERE inst.merchant_id=$1"
+ " AND ("
+ " (COALESCE (array_length ($2::TEXT[], 1), 0) > 0"
+ " AND minv.product_id = ANY ($2::TEXT[]))"
+ " OR"
+ " (COALESCE (array_length ($3::BIGINT[], 1), 0) > 0"
+ " AND EXISTS ("
+ " SELECT 1"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = minv.product_serial"
+ " AND mpc.category_serial = ANY ($3::BIGINT[])"
+ " ))"
+ " )");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "lookup_inventory_products_filtered",
+ params,
+ &lookup_inventory_products_cb,
+ &plc);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ /* If there was an error inside lookup_inventory_products_cb, return a hard error. */
+ if (plc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_inventory_products_filtered.h b/src/backenddb/pg_lookup_inventory_products_filtered.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_inventory_products_filtered.h
+ * @brief Lookup inventory product details for templates (filtered)
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_LOOKUP_INVENTORY_PRODUCTS_FILTERED_H
+#define PG_LOOKUP_INVENTORY_PRODUCTS_FILTERED_H
+
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup inventory details for a subset of products.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_ids product IDs to include (can be NULL/empty)
+ * @param num_product_ids number of entries in @a product_ids
+ * @param categories category IDs to include (can be NULL/empty)
+ * @param num_categories number of entries in @a categories
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_inventory_products_filtered (
+ void *cls,
+ const char *instance_id,
+ const char *const *product_ids,
+ size_t num_product_ids,
+ const uint64_t *categories,
+ size_t num_categories,
+ TALER_MERCHANTDB_InventoryProductCallback cb,
+ void *cb_cls);
+
+#endif /* PG_LOOKUP_INVENTORY_PRODUCTS_FILTERED_H */
diff --git a/src/backenddb/pg_lookup_product.c b/src/backenddb/pg_lookup_product.c
@@ -173,6 +173,7 @@ TMH_PG_lookup_product (void *cls,
pd->unit = my_unit;
pd->taxes = my_taxes;
pd->image = my_image;
+ pd->image_hash = NULL;
pd->address = my_address;
pd->price_array = my_price_array;
pd->price_array_length = my_price_array_length;
diff --git a/src/backenddb/pg_lookup_template.c b/src/backenddb/pg_lookup_template.c
@@ -48,6 +48,7 @@ TMH_PG_lookup_template (void *cls,
GNUNET_PQ_query_param_string (template_id),
GNUNET_PQ_query_param_end
};
+ enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
@@ -70,10 +71,12 @@ TMH_PG_lookup_template (void *cls,
GNUNET_PQ_result_spec_end
};
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_template",
- params,
- rs_null);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_template",
+ params,
+ rs_null);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
}
else
{
@@ -96,9 +99,11 @@ TMH_PG_lookup_template (void *cls,
memset (td,
0,
sizeof (*td));
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_template",
- params,
- rs);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_template",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
}
}
diff --git a/src/backenddb/pg_update_template.c b/src/backenddb/pg_update_template.c
@@ -46,6 +46,7 @@ TMH_PG_update_template (void *cls,
: TALER_PQ_query_param_json (td->editable_defaults),
GNUNET_PQ_query_param_end
};
+ enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
@@ -70,7 +71,9 @@ TMH_PG_update_template (void *cls,
" (SELECT merchant_serial"
" FROM mid)"
" AND template_id=$2");
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_template",
- params);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_template",
+ params);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
}
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
@@ -50,7 +50,9 @@
#include "pg_lookup_transfers.h"
#include "pg_lookup_pending_deposits.h"
#include "pg_lookup_categories.h"
+#include "pg_lookup_categories_by_ids.h"
#include "pg_lookup_units.h"
+#include "pg_lookup_custom_units_by_names.h"
#include "pg_select_category.h"
#include "pg_update_category.h"
#include "pg_insert_category.h"
@@ -103,6 +105,8 @@
#include "pg_activate_account.h"
#include "pg_lookup_products.h"
#include "pg_lookup_all_products.h"
+#include "pg_lookup_inventory_products.h"
+#include "pg_lookup_inventory_products_filtered.h"
#include "pg_lookup_product.h"
#include "pg_lookup_product_image.h"
#include "pg_lookup_statistics_amount_by_bucket2.h"
@@ -489,6 +493,10 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_lookup_products;
plugin->lookup_all_products
= &TMH_PG_lookup_all_products;
+ plugin->lookup_inventory_products
+ = &TMH_PG_lookup_inventory_products;
+ plugin->lookup_inventory_products_filtered
+ = &TMH_PG_lookup_inventory_products_filtered;
plugin->lookup_product
= &TMH_PG_lookup_product;
plugin->lookup_product_image_by_hash
@@ -649,8 +657,12 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_update_pending_webhook;
plugin->lookup_categories
= &TMH_PG_lookup_categories;
+ plugin->lookup_categories_by_ids
+ = &TMH_PG_lookup_categories_by_ids;
plugin->lookup_units
= &TMH_PG_lookup_units;
+ plugin->lookup_custom_units_by_names
+ = &TMH_PG_lookup_custom_units_by_names;
plugin->select_category_by_name
= &TMH_PG_select_category_by_name;
plugin->get_kyc_status
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
@@ -1055,7 +1055,7 @@ TALER_MERCHANT_instance_delete_cancel (
* @param arg request to cancel.
*/
#define TALER_MERCHANT_instance_purge_cancel(arg) \
- TALER_MERCHANT_instance_delete_cancel (arg)
+ TALER_MERCHANT_instance_delete_cancel (arg)
/* *************** Accounts **************** */
@@ -2020,6 +2020,67 @@ TALER_MERCHANT_products_post4 (
TALER_MERCHANT_ProductsPostCallback cb,
void *cb_cls);
+/**
+ * Response from a POST /private/categories request.
+ */
+struct TALER_MERCHANT_CategoriesPostResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Category id on success.
+ */
+ uint64_t category_id;
+};
+
+/**
+ * Callback for POST /private/categories.
+ *
+ * @param cls closure
+ * @param cpr response details
+ */
+typedef void
+(*TALER_MERCHANT_CategoriesPostCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_CategoriesPostResponse *cpr);
+
+/**
+ * Handle for a POST /private/categories operation.
+ */
+struct TALER_MERCHANT_CategoriesPostHandle;
+
+/**
+ * Make a POST /private/categories request to add a category.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param name category name
+ * @param name_i18n Map from IETF BCP 47 language tags to localized names
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_CategoriesPostHandle *
+TALER_MERCHANT_categories_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *name,
+ const json_t *name_i18n,
+ TALER_MERCHANT_CategoriesPostCallback cb,
+ void *cb_cls);
+
+/**
+ * Cancel POST /private/categories operation.
+ *
+ * @param cph operation to cancel
+ */
+void
+TALER_MERCHANT_categories_post_cancel (
+ struct TALER_MERCHANT_CategoriesPostHandle *cph);
+
/**
* Cancel POST /products operation.
@@ -5951,6 +6012,27 @@ TALER_MERCHANT_using_templates_post (
TALER_MERCHANT_PostOrdersCallback cb,
void *cb_cls);
+/**
+ * Make a POST /templates/$ID request to instantiate a template
+ * with a raw JSON payload. Useful for inventory templates.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param template_id identifier of the template to use
+ * @param details JSON payload to send as request body
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_UsingTemplatesPostHandle *
+TALER_MERCHANT_using_templates_post2 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const json_t *details,
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls);
+
/**
* Cancel POST /using-templates operation.
diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h
@@ -38,7 +38,7 @@
#define MERCHANT_FAIL() \
- do {GNUNET_break (0); return NULL; } while (0)
+ do {GNUNET_break (0); return NULL; } while (0)
/**
@@ -434,6 +434,86 @@ TALER_TESTING_cmd_merchant_post_products (const char *label,
const char *price,
unsigned int http_status);
+/**
+ * Define a "POST /products" CMD with category IDs.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /products request.
+ * @param product_id the ID of the product to create
+ * @param description name of the product
+ * @param price price of the product
+ * @param num_cats length of the @a cats array
+ * @param cats array of category IDs
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_products_with_categories (
+ const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ const char *description,
+ const char *unit,
+ const char *price,
+ unsigned int num_cats,
+ const uint64_t *cats,
+ bool unit_allow_fraction,
+ uint32_t unit_precision_level,
+ unsigned int http_status);
+
+/**
+ * Define a "POST /private/categories" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the request.
+ * @param name category name
+ * @param name_i18n category name translations
+ * @param expected_category_id expected category id (0 to ignore)
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_categories (
+ const char *label,
+ const char *merchant_url,
+ const char *name,
+ json_t *name_i18n,
+ uint64_t expected_category_id,
+ unsigned int http_status);
+
+/**
+ * Define a "GET /templates/$ID" wallet CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the request.
+ * @param template_id template ID to fetch
+ * @param expected_products_len expected number of products (0 to ignore)
+ * @param expected_product_id first expected product id (can be NULL)
+ * @param expected_unit expected unit for @a expected_product_id (can be NULL)
+ * @param expected_unit_allow_fraction expected fractional flag
+ * @param expected_unit_precision_level expected precision
+ * @param expected_product_id2 second expected product id (can be NULL)
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_wallet_get_template (
+ const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ size_t expected_products_len,
+ const char *expected_product_id,
+ const char *expected_unit,
+ bool expected_unit_allow_fraction,
+ uint32_t expected_unit_precision_level,
+ json_t *expected_unit_name_short_i18n,
+ const char *expected_product_id2,
+ const char *expected_product_id3,
+ uint64_t expected_category_id1,
+ uint64_t expected_category_id2,
+ unsigned int http_status);
+
/**
* Define a "PATCH /products/$ID" CMD.
@@ -1877,6 +1957,29 @@ TALER_TESTING_cmd_merchant_post_using_templates (
struct GNUNET_TIME_Timestamp pay_deadline,
unsigned int http_status);
+/**
+ * Define a "POST /using-templates" CMD with a raw JSON request body.
+ *
+ * @param label command label.
+ * @param template_ref label of command that created the template to use
+ * @param otp_ref label of command that created OTP device we use (or NULL for no OTP)
+ * @param merchant_url base URL of the merchant serving the
+ * POST /using-templates request.
+ * @param using_template_id template ID to use
+ * @param details raw JSON request body to send
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_using_templates2 (
+ const char *label,
+ const char *template_ref,
+ const char *otp_ref,
+ const char *merchant_url,
+ const char *using_template_id,
+ const json_t *details,
+ unsigned int http_status);
+
/* ****** Token Families ******* */
@@ -2270,65 +2373,66 @@ TALER_TESTING_cmd_exec_donaukeyupdate (const char *label,
*/
// FIXME: rename: refund_entry->refund_detail
#define TALER_MERCHANT_TESTING_SIMPLE_TRAITS(op) \
- op (claim_nonce, const struct GNUNET_CRYPTO_EddsaPublicKey) \
- op (pickup_id, const struct TALER_PickupIdentifierP) \
- op (instance_name, const char) \
- op (instance_id, const char) \
- op (address, const json_t) \
- op (product_description, const char) \
- op (product_image, const char) \
- op (product_stock, const int64_t) \
- op (product_unit_total_stock, const char) \
- op (product_unit_precision_level, const uint32_t) \
- op (product_unit_allow_fraction, const bool) \
- op (product_unit, const char) \
- op (product_id, const char) \
- op (unit_id, const char) \
- op (unit_name_long, const char) \
- op (unit_name_short, const char) \
- op (unit_allow_fraction, const bool) \
- op (unit_precision_level, const uint32_t) \
- op (unit_active, const bool) \
- op (unit_builtin, const bool) \
- op (unit_name_long_i18n, const json_t) \
- op (unit_name_short_i18n, const json_t) \
- op (reason, const char) \
- op (lock_uuid, const char) \
- op (auth_token, const char) \
- op (bearer_token, const char) \
- op (paths_length, const uint32_t) \
- op (payto_length, const uint32_t) \
- op (num_planchets, const uint32_t) \
- op (i18n_description, const json_t) \
- op (taxes, const json_t) \
- op (fee, const struct TALER_Amount) \
- op (use_stefan, const bool) \
- op (jurisdiction, const json_t) \
- op (wire_delay, const struct GNUNET_TIME_Relative) \
- op (pay_delay, const struct GNUNET_TIME_Relative) \
- op (refund_entry, const struct TALER_MERCHANT_RefundDetail) \
- op (order_terms, const json_t) \
- op (h_contract_terms, const struct TALER_PrivateContractHashP) \
- op (h_wire, const struct TALER_MerchantWireHashP) \
- op (proposal_reference, const char) \
- op (template_description, const char) \
- op (otp_device_description, const char) \
- op (otp_id, const char) \
- op (otp_key, const char) \
- op (otp_alg, const enum TALER_MerchantConfirmationAlgorithm) \
- op (template_id, const char) \
- op (template_contract, const json_t) \
- op (event_type, const char) \
- op (webhook_id, const char) \
- op (merchant_base_url, const char) \
- op (url, const char) \
- op (http_method, const char) \
- op (header_template, const char) \
- op (body_template, const char) \
- op (summary, const char) \
- op (token_family_slug, const char) \
- op (token_family_duration, const struct GNUNET_TIME_Relative) \
- op (token_family_kind, const char)
+ op (claim_nonce, const struct GNUNET_CRYPTO_EddsaPublicKey) \
+ op (pickup_id, const struct TALER_PickupIdentifierP) \
+ op (instance_name, const char) \
+ op (instance_id, const char) \
+ op (address, const json_t) \
+ op (category_id, const uint64_t) \
+ op (product_description, const char) \
+ op (product_image, const char) \
+ op (product_stock, const int64_t) \
+ op (product_unit_total_stock, const char) \
+ op (product_unit_precision_level, const uint32_t) \
+ op (product_unit_allow_fraction, const bool) \
+ op (product_unit, const char) \
+ op (product_id, const char) \
+ op (unit_id, const char) \
+ op (unit_name_long, const char) \
+ op (unit_name_short, const char) \
+ op (unit_allow_fraction, const bool) \
+ op (unit_precision_level, const uint32_t) \
+ op (unit_active, const bool) \
+ op (unit_builtin, const bool) \
+ op (unit_name_long_i18n, const json_t) \
+ op (unit_name_short_i18n, const json_t) \
+ op (reason, const char) \
+ op (lock_uuid, const char) \
+ op (auth_token, const char) \
+ op (bearer_token, const char) \
+ op (paths_length, const uint32_t) \
+ op (payto_length, const uint32_t) \
+ op (num_planchets, const uint32_t) \
+ op (i18n_description, const json_t) \
+ op (taxes, const json_t) \
+ op (fee, const struct TALER_Amount) \
+ op (use_stefan, const bool) \
+ op (jurisdiction, const json_t) \
+ op (wire_delay, const struct GNUNET_TIME_Relative) \
+ op (pay_delay, const struct GNUNET_TIME_Relative) \
+ op (refund_entry, const struct TALER_MERCHANT_RefundDetail) \
+ op (order_terms, const json_t) \
+ op (h_contract_terms, const struct TALER_PrivateContractHashP) \
+ op (h_wire, const struct TALER_MerchantWireHashP) \
+ op (proposal_reference, const char) \
+ op (template_description, const char) \
+ op (otp_device_description, const char) \
+ op (otp_id, const char) \
+ op (otp_key, const char) \
+ op (otp_alg, const enum TALER_MerchantConfirmationAlgorithm) \
+ op (template_id, const char) \
+ op (template_contract, const json_t) \
+ op (event_type, const char) \
+ op (webhook_id, const char) \
+ op (merchant_base_url, const char) \
+ op (url, const char) \
+ op (http_method, const char) \
+ op (header_template, const char) \
+ op (body_template, const char) \
+ op (summary, const char) \
+ op (token_family_slug, const char) \
+ op (token_family_duration, const struct GNUNET_TIME_Relative) \
+ op (token_family_kind, const char)
/**
diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h
@@ -24,6 +24,7 @@
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
+#include <string.h>
#include <stdint.h>
#include <taler/taler_util.h>
#include <jansson.h>
@@ -206,6 +207,40 @@ enum TALER_MERCHANT_MFA_CriticalOperation
};
+/**
+ * Template type discriminator.
+ */
+enum TALER_MERCHANT_TemplateType
+{
+ TALER_MERCHANT_TEMPLATE_TYPE_INVALID = 0,
+ TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER,
+ TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART,
+ TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA
+};
+
+/**
+ * Determine template type from string.
+ *
+ * @param template_type string value (NULL means fixed-order)
+ * @return template type (defaults to fixed order)
+ */
+static inline enum TALER_MERCHANT_TemplateType
+TALER_MERCHANT_template_type_from_string (const char *template_type)
+{
+ if (NULL == template_type)
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+ if (0 == strcmp (template_type,
+ "fixed-order"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
+ if (0 == strcmp (template_type,
+ "inventory-cart"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
+ if (0 == strcmp (template_type,
+ "paivana"))
+ return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
+ return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
+}
+
/**
* Convert critical operation enumeration value to string
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -362,6 +362,11 @@ struct TALER_MERCHANTDB_ProductDetails
char *image;
/**
+ * Hash of the product image data, or NULL.
+ */
+ char *image_hash;
+
+ /**
* List of taxes the merchant pays for this product. Never NULL,
* but can be an empty array.
*/
@@ -446,6 +451,70 @@ struct TALER_MERCHANTDB_ProductDetails
};
+/**
+ * Minimal product details for inventory templates.
+ */
+struct TALER_MERCHANTDB_InventoryProductDetails
+{
+ /**
+ * Name of the product.
+ */
+ char *product_name;
+
+ /**
+ * Description of the product.
+ */
+ char *description;
+
+ /**
+ * Internationalized description.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Unit in which the product is sold.
+ */
+ char *unit;
+
+ /**
+ * Price per unit of the product.
+ */
+ struct TALER_Amount price;
+
+ /**
+ * Optional list of per-unit prices. When NULL or empty, @e price
+ * must be used as the canonical single price.
+ */
+ struct TALER_Amount *price_array;
+
+ /**
+ * Number of entries in @e price_array.
+ */
+ size_t price_array_length;
+
+ /**
+ * Hash of the product image data, or NULL.
+ */
+ char *image_hash;
+
+ /**
+ * Honor fractional stock if TRUE, else only integer stock.
+ */
+ bool allow_fractional_quantity;
+
+ /**
+ * Precision level (number of decimal places) to apply when
+ * fractional quantities are enabled.
+ */
+ uint32_t fractional_precision_level;
+
+ /**
+ * List of taxes the merchant pays for this product. Never NULL,
+ * but can be an empty array.
+ */
+ json_t *taxes;
+};
+
/**
* Details about an inventory measurement unit.
@@ -525,6 +594,24 @@ typedef void
size_t num_categories,
const uint64_t *categories);
+/**
+ * Typically called by `lookup_inventory_products`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param product_id ID of the product
+ * @param pd inventory product details
+ * @param num_categories length of @a categories array
+ * @param categories array of categories the
+ * product is in
+ */
+typedef void
+(*TALER_MERCHANTDB_InventoryProductCallback)(
+ void *cls,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_InventoryProductDetails *pd,
+ size_t num_categories,
+ const uint64_t *categories);
+
/**
* Typically called by `lookup_login_tokens`.
@@ -1800,7 +1887,7 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
*/
enum GNUNET_GenericReturnValue
- (*connect)(void *cls);
+ (*connect)(void *cls);
/**
* Drop merchant tables. Used for testcases and to reset the DB.
@@ -1809,7 +1896,7 @@ struct TALER_MERCHANTDB_Plugin
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
enum GNUNET_GenericReturnValue
- (*drop_tables)(void *cls);
+ (*drop_tables)(void *cls);
/**
* Garbage collect database. Removes unnecessary data.
@@ -1818,7 +1905,7 @@ struct TALER_MERCHANTDB_Plugin
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
enum GNUNET_GenericReturnValue
- (*gc)(void *cls);
+ (*gc)(void *cls);
/**
* Initialize merchant tables
@@ -1827,7 +1914,7 @@ struct TALER_MERCHANTDB_Plugin
* @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
*/
enum GNUNET_GenericReturnValue
- (*create_tables)(void *cls);
+ (*create_tables)(void *cls);
/**
* Register callback to be invoked on events of type @a es.
@@ -1890,8 +1977,8 @@ struct TALER_MERCHANTDB_Plugin
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
- (*start)(void *cls,
- const char *name);
+ (*start)(void *cls,
+ const char *name);
/**
* Start a transaction with isolation level 'read committed'.
@@ -1902,8 +1989,8 @@ struct TALER_MERCHANTDB_Plugin
* @return #GNUNET_OK on success
*/
enum GNUNET_GenericReturnValue
- (*start_read_committed)(void *cls,
- const char *name);
+ (*start_read_committed)(void *cls,
+ const char *name);
/**
* Roll back the current transaction of a database connection.
@@ -1920,7 +2007,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*commit)(void *cls);
+ (*commit)(void *cls);
/**
* Lookup all of the instances this backend has configured.
@@ -1931,10 +2018,10 @@ struct TALER_MERCHANTDB_Plugin
* @param cb_cls closure for @a cb
*/
enum GNUNET_DB_QueryStatus
- (*lookup_instances)(void *cls,
- bool active_only,
- TALER_MERCHANTDB_InstanceCallback cb,
- void *cb_cls);
+ (*lookup_instances)(void *cls,
+ bool active_only,
+ TALER_MERCHANTDB_InstanceCallback cb,
+ void *cb_cls);
/**
* Lookup one of the instances this backend has configured.
@@ -1946,11 +2033,11 @@ struct TALER_MERCHANTDB_Plugin
* @param cb_cls closure for @a cb
*/
enum GNUNET_DB_QueryStatus
- (*lookup_instance)(void *cls,
- const char *id,
- bool active_only,
- TALER_MERCHANTDB_InstanceCallback cb,
- void *cb_cls);
+ (*lookup_instance)(void *cls,
+ const char *id,
+ bool active_only,
+ TALER_MERCHANTDB_InstanceCallback cb,
+ void *cb_cls);
/**
* Lookup authentication data of an instance.
@@ -1960,9 +2047,9 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] ias where to store the auth data
*/
enum GNUNET_DB_QueryStatus
- (*lookup_instance_auth)(void *cls,
- const char *instance_id,
- struct TALER_MERCHANTDB_InstanceAuthSettings *ias);
+ (*lookup_instance_auth)(void *cls,
+ const char *instance_id,
+ struct TALER_MERCHANTDB_InstanceAuthSettings *ias);
/**
@@ -1977,12 +2064,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_instance)(void *cls,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantPrivateKeyP *merchant_priv,
- const struct TALER_MERCHANTDB_InstanceSettings *is,
- const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
- bool validation_needed);
+ (*insert_instance)(void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const struct TALER_MERCHANTDB_InstanceSettings *is,
+ const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
+ bool validation_needed);
/**
@@ -1993,7 +2080,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_account)(
+ (*insert_account)(
void *cls,
const struct TALER_MERCHANTDB_AccountDetails *account_details);
@@ -2011,7 +2098,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_login_token)(
+ (*insert_login_token)(
void *cls,
const char *id,
const struct TALER_MERCHANTDB_LoginTokenP *token,
@@ -2032,7 +2119,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_login_token)(
+ (*select_login_token)(
void *cls,
const char *id,
const struct TALER_MERCHANTDB_LoginTokenP *token,
@@ -2052,12 +2139,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_login_tokens)(void *cls,
- const char *instance_id,
- uint64_t offset,
- int64_t limit,
- TALER_MERCHANTDB_LoginTokensCallback cb,
- void *cb_cls);
+ (*lookup_login_tokens)(void *cls,
+ const char *instance_id,
+ uint64_t offset,
+ int64_t limit,
+ TALER_MERCHANTDB_LoginTokensCallback cb,
+ void *cb_cls);
/**
* Delete login token from database.
@@ -2068,7 +2155,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_login_token)(
+ (*delete_login_token)(
void *cls,
const char *id,
const struct TALER_MERCHANTDB_LoginTokenP *token);
@@ -2082,7 +2169,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_login_token_serial)(
+ (*delete_login_token_serial)(
void *cls,
const char *id,
uint64_t serial);
@@ -2099,7 +2186,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_account)(
+ (*update_account)(
void *cls,
const char *id,
const struct TALER_MerchantWireHashP *h_wire,
@@ -2117,7 +2204,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_accounts)(
+ (*select_accounts)(
void *cls,
const char *id,
TALER_MERCHANTDB_AccountCallback cb,
@@ -2134,7 +2221,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_account)(
+ (*select_account)(
void *cls,
const char *id,
const struct TALER_MerchantWireHashP *h_wire,
@@ -2151,7 +2238,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_account_by_uri)(
+ (*select_account_by_uri)(
void *cls,
const char *id,
struct TALER_FullPayto payto_uri,
@@ -2166,7 +2253,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_instance_private_key)(
+ (*delete_instance_private_key)(
void *cls,
const char *merchant_id);
@@ -2179,8 +2266,8 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*purge_instance)(void *cls,
- const char *merchant_id);
+ (*purge_instance)(void *cls,
+ const char *merchant_id);
/**
* Update information about an instance into our database.
@@ -2190,8 +2277,8 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_instance)(void *cls,
- const struct TALER_MERCHANTDB_InstanceSettings *is);
+ (*update_instance)(void *cls,
+ const struct TALER_MERCHANTDB_InstanceSettings *is);
/**
* Update information about an instance's authentication settings
@@ -2203,7 +2290,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_instance_auth)(
+ (*update_instance_auth)(
void *cls,
const char *merchant_id,
const struct TALER_MERCHANTDB_InstanceAuthSettings *ias);
@@ -2217,9 +2304,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*inactivate_account)(void *cls,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire);
+ (*inactivate_account)(void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire);
/**
@@ -2231,9 +2318,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*activate_account)(void *cls,
- const char *merchant_id,
- const struct TALER_MerchantWireHashP *h_wire);
+ (*activate_account)(void *cls,
+ const char *merchant_id,
+ const struct TALER_MerchantWireHashP *h_wire);
/**
@@ -2250,7 +2337,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*account_kyc_get_status)(
+ (*account_kyc_get_status)(
void *cls,
const char *merchant_id,
const struct TALER_MerchantWireHashP *h_wire,
@@ -2279,7 +2366,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*get_kyc_status)(
+ (*get_kyc_status)(
void *cls,
struct TALER_FullPayto merchant_account_uri,
const char *instance_id,
@@ -2308,7 +2395,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*get_kyc_limits)(
+ (*get_kyc_limits)(
void *cls,
struct TALER_FullPayto merchant_account_uri,
const char *instance_id,
@@ -2337,7 +2424,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*account_kyc_set_status)(
+ (*account_kyc_set_status)(
void *cls,
const char *merchant_id,
const struct TALER_MerchantWireHashP *h_wire,
@@ -2365,7 +2452,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*account_kyc_set_failed)(
+ (*account_kyc_set_failed)(
void *cls,
const char *merchant_id,
const struct TALER_MerchantWireHashP *h_wire,
@@ -2409,15 +2496,15 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_products)(void *cls,
- const char *instance_id,
- uint64_t offset,
- int64_t limit,
- const char *category_filter,
- const char *name_filter,
- const char *description_filter,
- TALER_MERCHANTDB_ProductsCallback cb,
- void *cb_cls);
+ (*lookup_products)(void *cls,
+ const char *instance_id,
+ uint64_t offset,
+ int64_t limit,
+ const char *category_filter,
+ const char *name_filter,
+ const char *description_filter,
+ TALER_MERCHANTDB_ProductsCallback cb,
+ void *cb_cls);
/**
@@ -2430,10 +2517,49 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_all_products)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_ProductCallback cb,
- void *cb_cls);
+ (*lookup_all_products)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_ProductCallback cb,
+ void *cb_cls);
+
+ /**
+ * Lookup inventory details for all products of an instance.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_inventory_products)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_InventoryProductCallback cb,
+ void *cb_cls);
+
+ /**
+ * Lookup inventory details for a subset of products.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_ids product IDs to include (can be NULL/empty)
+ * @param num_product_ids number of entries in @a product_ids
+ * @param categories category IDs to include (can be NULL/empty)
+ * @param num_categories number of entries in @a categories
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_inventory_products_filtered)(
+ void *cls,
+ const char *instance_id,
+ const char *const *product_ids,
+ size_t num_product_ids,
+ const uint64_t *categories,
+ size_t num_categories,
+ TALER_MERCHANTDB_InventoryProductCallback cb,
+ void *cb_cls);
/**
* Lookup details about a particular product.
@@ -2449,12 +2575,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_product)(void *cls,
- const char *instance_id,
- const char *product_id,
- struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t *num_categories,
- uint64_t **categories);
+ (*lookup_product)(void *cls,
+ const char *instance_id,
+ const char *product_id,
+ struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t *num_categories,
+ uint64_t **categories);
/**
* Lookup product image by its hash.
@@ -2466,10 +2592,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_product_image_by_hash)(void *cls,
- const char *instance_id,
- const char *image_hash,
- char **image);
+ (*lookup_product_image_by_hash)(void *cls,
+ const char *instance_id,
+ const char *image_hash,
+ char **image);
/**
* Delete information about a product. Note that the transaction must
@@ -2482,9 +2608,9 @@ struct TALER_MERCHANTDB_Plugin
* if locks prevent deletion OR product unknown
*/
enum GNUNET_DB_QueryStatus
- (*delete_product)(void *cls,
- const char *instance_id,
- const char *product_id);
+ (*delete_product)(void *cls,
+ const char *instance_id,
+ const char *product_id);
/**
* Insert details about a particular product.
@@ -2504,18 +2630,15 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_product)(void *cls,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t num_cats,
- const uint64_t *cats,
- bool *no_instance,
- bool *conflict,
- ssize_t *no_cat,
- bool *no_group,
- bool *no_pot);
-
+ (*insert_product)(void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ bool *conflict,
+ ssize_t *no_cat);
/**
* Update details about a particular product. Note that the
@@ -2541,20 +2664,18 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_product)(void *cls,
- const char *instance_id,
- const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd,
- size_t num_cats,
- const uint64_t *cats,
- bool *no_instance,
- ssize_t *no_cat,
- bool *no_product,
- bool *lost_reduced,
- bool *sold_reduced,
- bool *stocked_reduced,
- bool *no_group,
- bool *no_pot);
+ (*update_product)(void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ ssize_t *no_cat,
+ bool *no_product,
+ bool *lost_reduced,
+ bool *sold_reduced,
+ bool *stocked_reduced);
/**
@@ -2573,13 +2694,13 @@ struct TALER_MERCHANTDB_Plugin
* product is unknown OR if there insufficient stocks remaining
*/
enum GNUNET_DB_QueryStatus
- (*lock_product)(void *cls,
- const char *instance_id,
- const char *product_id,
- const struct GNUNET_Uuid *uuid,
- uint64_t quantity,
- uint32_t quantity_frac,
- struct GNUNET_TIME_Timestamp expiration_time);
+ (*lock_product)(void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct GNUNET_Uuid *uuid,
+ uint64_t quantity,
+ uint32_t quantity_frac,
+ struct GNUNET_TIME_Timestamp expiration_time);
/**
@@ -2591,7 +2712,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*expire_locks)(void *cls);
+ (*expire_locks)(void *cls);
/**
@@ -2606,10 +2727,10 @@ struct TALER_MERCHANTDB_Plugin
* if locks prevent deletion OR order unknown
*/
enum GNUNET_DB_QueryStatus
- (*delete_order)(void *cls,
- const char *instance_id,
- const char *order_id,
- bool force);
+ (*delete_order)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ bool force);
/**
@@ -2626,12 +2747,12 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_order)(void *cls,
- const char *instance_id,
- const char *order_id,
- struct TALER_ClaimTokenP *claim_token,
- struct TALER_MerchantPostDataHashP *h_post_data,
- json_t **contract_terms);
+ (*lookup_order)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct TALER_ClaimTokenP *claim_token,
+ struct TALER_MerchantPostDataHashP *h_post_data,
+ json_t **contract_terms);
/**
@@ -2645,11 +2766,11 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_order_summary)(void *cls,
- const char *instance_id,
- const char *order_id,
- struct GNUNET_TIME_Timestamp *timestamp,
- uint64_t *order_serial);
+ (*lookup_order_summary)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp *timestamp,
+ uint64_t *order_serial);
/**
@@ -2663,11 +2784,11 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_orders)(void *cls,
- const char *instance_id,
- const struct TALER_MERCHANTDB_OrderFilter *of,
- TALER_MERCHANTDB_OrdersCallback cb,
- void *cb_cls);
+ (*lookup_orders)(void *cls,
+ const char *instance_id,
+ const struct TALER_MERCHANTDB_OrderFilter *of,
+ TALER_MERCHANTDB_OrdersCallback cb,
+ void *cb_cls);
/**
@@ -2686,16 +2807,16 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_order)(void *cls,
- const char *instance_id,
- const char *order_id,
- const char *session_id,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- struct GNUNET_TIME_Timestamp pay_deadline,
- const struct TALER_ClaimTokenP *claim_token,
- const json_t *contract_terms,
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm);
+ (*insert_order)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *session_id,
+ const struct TALER_MerchantPostDataHashP *h_post_data,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const struct TALER_ClaimTokenP *claim_token,
+ const json_t *contract_terms,
+ const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm);
/**
@@ -2708,7 +2829,7 @@ struct TALER_MERCHANTDB_Plugin
* @param blinded_sigs JSON array of blinded signatures
*/
enum GNUNET_DB_QueryStatus
- (*insert_order_blinded_sigs)(
+ (*insert_order_blinded_sigs)(
void *cls,
const char *order_id,
uint32_t i,
@@ -2727,8 +2848,8 @@ struct TALER_MERCHANTDB_Plugin
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
*/
enum GNUNET_DB_QueryStatus
- (*unlock_inventory)(void *cls,
- const struct GNUNET_Uuid *uuid);
+ (*unlock_inventory)(void *cls,
+ const struct GNUNET_Uuid *uuid);
/**
@@ -2746,12 +2867,12 @@ struct TALER_MERCHANTDB_Plugin
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
*/
enum GNUNET_DB_QueryStatus
- (*insert_order_lock)(void *cls,
- const char *instance_id,
- const char *order_id,
- const char *product_id,
- uint64_t quantity,
- uint32_t quantity_frac);
+ (*insert_order_lock)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *product_id,
+ uint64_t quantity,
+ uint32_t quantity_frac);
/**
@@ -2763,7 +2884,7 @@ struct TALER_MERCHANTDB_Plugin
* @param cb_cls closure for @a cb
*/
enum GNUNET_DB_QueryStatus
- (*select_order_blinded_sigs)(
+ (*select_order_blinded_sigs)(
void *cls,
const char *order_id,
TALER_MERCHANTDB_BlindedSigCallback cb,
@@ -2785,7 +2906,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_contract_terms2)(
+ (*lookup_contract_terms2)(
void *cls,
const char *instance_id,
const char *order_id,
@@ -2814,7 +2935,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_contract_terms3)(
+ (*lookup_contract_terms3)(
void *cls,
const char *instance_id,
const char *order_id,
@@ -2840,7 +2961,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_contract_terms)(
+ (*lookup_contract_terms)(
void *cls,
const char *instance_id,
const char *order_id,
@@ -2866,7 +2987,7 @@ struct TALER_MERCHANTDB_Plugin
* is malformed
*/
enum GNUNET_DB_QueryStatus
- (*insert_contract_terms)(
+ (*insert_contract_terms)(
void *cls,
const char *instance_id,
const char *order_id,
@@ -2890,10 +3011,10 @@ struct TALER_MERCHANTDB_Plugin
* is malformed
*/
enum GNUNET_DB_QueryStatus
- (*update_contract_terms)(void *cls,
- const char *instance_id,
- const char *order_id,
- json_t *contract_terms);
+ (*update_contract_terms)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ json_t *contract_terms);
/**
@@ -2910,10 +3031,10 @@ struct TALER_MERCHANTDB_Plugin
* if locks prevent deletion OR order unknown
*/
enum GNUNET_DB_QueryStatus
- (*delete_contract_terms)(void *cls,
- const char *instance_id,
- const char *order_id,
- struct GNUNET_TIME_Relative legal_expiration);
+ (*delete_contract_terms)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct GNUNET_TIME_Relative legal_expiration);
/**
@@ -2928,12 +3049,12 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_deposits)(void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms
- ,
- TALER_MERCHANTDB_DepositsCallback cb,
- void *cb_cls);
+ (*lookup_deposits)(void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms
+ ,
+ TALER_MERCHANTDB_DepositsCallback cb,
+ void *cb_cls);
/**
@@ -2948,7 +3069,7 @@ struct TALER_MERCHANTDB_Plugin
* @param master_sig signature of @a master_pub over the @a exchange_pub and the dates
*/
enum GNUNET_DB_QueryStatus
- (*insert_exchange_signkey)(
+ (*insert_exchange_signkey)(
void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_ExchangePublicKeyP *exchange_pub,
@@ -2976,7 +3097,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_deposit_confirmation)(
+ (*insert_deposit_confirmation)(
void *cls,
const char *instance_id,
struct GNUNET_TIME_Timestamp deposit_timestamp,
@@ -3008,7 +3129,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_deposit)(
+ (*insert_deposit)(
void *cls,
uint32_t offset,
uint64_t deposit_confirmation_serial_id,
@@ -3031,11 +3152,11 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_refunds)(void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- TALER_MERCHANTDB_RefundCallback rc,
- void *rc_cls);
+ (*lookup_refunds)(void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ TALER_MERCHANTDB_RefundCallback rc,
+ void *rc_cls);
/**
@@ -3048,10 +3169,10 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_spent_tokens_by_order)(void *cls,
- uint64_t order_serial,
- TALER_MERCHANTDB_UsedTokensCallback cb,
- void *cb_cls);
+ (*lookup_spent_tokens_by_order)(void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_UsedTokensCallback cb,
+ void *cb_cls);
/**
@@ -3066,7 +3187,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*mark_contract_paid)(
+ (*mark_contract_paid)(
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
@@ -3087,7 +3208,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*update_contract_session)(
+ (*update_contract_session)(
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
@@ -3112,12 +3233,12 @@ struct TALER_MERCHANTDB_Plugin
* regardless of whether it actually increased the refund
*/
enum GNUNET_DB_QueryStatus
- (*refund_coin)(void *cls,
- const char *instance_id,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- struct GNUNET_TIME_Timestamp refund_timestamp,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *reason);
+ (*refund_coin)(void *cls,
+ const char *instance_id,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp refund_timestamp,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const char *reason);
/**
@@ -3131,11 +3252,11 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_order_status)(void *cls,
- const char *instance_id,
- const char *order_id,
- struct TALER_PrivateContractHashP *h_contract_terms,
- bool *paid);
+ (*lookup_order_status)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ bool *paid);
/**
* Retrieve contract terms given its @a order_serial
@@ -3149,13 +3270,13 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_order_status_by_serial)(void *cls,
- const char *instance_id,
- uint64_t order_serial,
- char **order_id,
- struct TALER_PrivateContractHashP *
- h_contract_terms,
- bool *paid);
+ (*lookup_order_status_by_serial)(void *cls,
+ const char *instance_id,
+ uint64_t order_serial,
+ char **order_id,
+ struct TALER_PrivateContractHashP *
+ h_contract_terms,
+ bool *paid);
/**
@@ -3168,10 +3289,10 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_deposits_by_order)(void *cls,
- uint64_t order_serial,
- TALER_MERCHANTDB_DepositedCoinsCallback cb,
- void *cb_cls);
+ (*lookup_deposits_by_order)(void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_DepositedCoinsCallback cb,
+ void *cb_cls);
/**
@@ -3185,7 +3306,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_transfer_details_by_order)(
+ (*lookup_transfer_details_by_order)(
void *cls,
uint64_t order_serial,
TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
@@ -3206,7 +3327,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*update_transfer_status)(
+ (*update_transfer_status)(
void *cls,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
@@ -3231,7 +3352,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database transaction status
*/
enum GNUNET_DB_QueryStatus
- (*finalize_transfer_status)(
+ (*finalize_transfer_status)(
void *cls,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
@@ -3254,7 +3375,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*select_open_transfers)(
+ (*select_open_transfers)(
void *cls,
uint64_t limit,
TALER_MERCHANTDB_OpenTransferCallback cb,
@@ -3272,11 +3393,11 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_deposit_to_transfer)(void *cls,
- uint64_t deposit_serial,
- const struct TALER_MerchantWireHashP *h_wire,
- const char *exchange_url,
- const struct TALER_EXCHANGE_DepositData *dd);
+ (*insert_deposit_to_transfer)(void *cls,
+ uint64_t deposit_serial,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DepositData *dd);
/**
@@ -3287,8 +3408,8 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*mark_order_wired)(void *cls,
- uint64_t order_serial);
+ (*mark_order_wired)(void *cls,
+ uint64_t order_serial);
/**
@@ -3314,7 +3435,7 @@ struct TALER_MERCHANTDB_Plugin
* what was already refunded (idempotency!)
*/
enum TALER_MERCHANTDB_RefundStatus
- (*increase_refund)(
+ (*increase_refund)(
void *cls,
const char *instance_id,
const char *order_id,
@@ -3335,7 +3456,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_refunds_detailed)(
+ (*lookup_refunds_detailed)(
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
@@ -3352,7 +3473,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_refund_proof)(
+ (*insert_refund_proof)(
void *cls,
uint64_t refund_serial,
const struct TALER_ExchangeSignatureP *exchange_sig,
@@ -3371,7 +3492,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_spent_token)(
+ (*insert_spent_token)(
void *cls,
const struct TALER_PrivateContractHashP *
h_contract_terms,
@@ -3391,7 +3512,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_issued_token)(
+ (*insert_issued_token)(
void *cls,
const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
@@ -3408,7 +3529,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_refund_proof)(
+ (*lookup_refund_proof)(
void *cls,
uint64_t refund_serial,
struct TALER_ExchangeSignatureP *exchange_sig,
@@ -3429,7 +3550,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_order_by_fulfillment)(
+ (*lookup_order_by_fulfillment)(
void *cls,
const char *instance_id,
const char *fulfillment_url,
@@ -3447,7 +3568,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*update_wirewatch_progress)(
+ (*update_wirewatch_progress)(
void *cls,
const char *instance,
struct TALER_FullPayto payto_uri,
@@ -3463,7 +3584,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*select_wirewatch_accounts)(
+ (*select_wirewatch_accounts)(
void *cls,
TALER_MERCHANTDB_WirewatchWorkCallback cb,
void *cb_cls);
@@ -3482,7 +3603,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_transfer)(
+ (*insert_transfer)(
void *cls,
const char *instance_id,
const char *exchange_url,
@@ -3503,9 +3624,9 @@ struct TALER_MERCHANTDB_Plugin
* if deletion is prohibited OR transfer is unknown
*/
enum GNUNET_DB_QueryStatus
- (*delete_transfer)(void *cls,
- const char *instance_id,
- uint64_t transfer_serial_id);
+ (*delete_transfer)(void *cls,
+ const char *instance_id,
+ uint64_t transfer_serial_id);
/**
@@ -3519,9 +3640,9 @@ struct TALER_MERCHANTDB_Plugin
* if the transfer record exists
*/
enum GNUNET_DB_QueryStatus
- (*check_transfer_exists)(void *cls,
- const char *instance_id,
- uint64_t transfer_serial_id);
+ (*check_transfer_exists)(void *cls,
+ const char *instance_id,
+ uint64_t transfer_serial_id);
/**
@@ -3534,10 +3655,10 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_account)(void *cls,
- const char *instance_id,
- struct TALER_FullPayto payto_uri,
- uint64_t *account_serial);
+ (*lookup_account)(void *cls,
+ const char *instance_id,
+ struct TALER_FullPayto payto_uri,
+ uint64_t *account_serial);
/**
@@ -3554,7 +3675,7 @@ struct TALER_MERCHANTDB_Plugin
* #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success
*/
enum GNUNET_DB_QueryStatus
- (*insert_transfer_details)(
+ (*insert_transfer_details)(
void *cls,
const char *instance_id,
const char *exchange_url,
@@ -3578,7 +3699,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_wire_fee)(
+ (*lookup_wire_fee)(
void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
const char *wire_method,
@@ -3602,7 +3723,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_deposits_by_contract_and_coin)(
+ (*lookup_deposits_by_contract_and_coin)(
void *cls,
const char *instance_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
@@ -3622,7 +3743,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_transfer_summary)(
+ (*lookup_transfer_summary)(
void *cls,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
@@ -3641,7 +3762,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_transfer_details)(
+ (*lookup_transfer_details)(
void *cls,
const char *exchange_url,
const struct TALER_WireTransferIdentifierRawP *wtid,
@@ -3666,7 +3787,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_transfers)(
+ (*lookup_transfers)(
void *cls,
const char *instance_id,
struct TALER_FullPayto payto_uri,
@@ -3697,7 +3818,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_expected_transfers)(
+ (*lookup_expected_transfers)(
void *cls,
const char *instance_id,
struct TALER_FullPayto payto_uri,
@@ -3725,7 +3846,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*store_wire_fee_by_exchange)(
+ (*store_wire_fee_by_exchange)(
void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
const struct GNUNET_HashCode *h_wire_method,
@@ -3743,7 +3864,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*delete_exchange_accounts)(
+ (*delete_exchange_accounts)(
void *cls,
const struct TALER_MasterPublicKeyP *master_pub);
@@ -3758,7 +3879,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*select_accounts_by_exchange)(
+ (*select_accounts_by_exchange)(
void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
TALER_MERCHANTDB_ExchangeAccountCallback cb,
@@ -3778,7 +3899,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*insert_exchange_account)(
+ (*insert_exchange_account)(
void *cls,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_FullPayto payto_uri,
@@ -3798,10 +3919,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_templates)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_TemplatesCallback cb,
- void *cb_cls);
+ (*lookup_templates)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TemplatesCallback cb,
+ void *cb_cls);
/**
@@ -3815,10 +3936,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_template)(void *cls,
- const char *instance_id,
- const char *template_id,
- struct TALER_MERCHANTDB_TemplateDetails *td);
+ (*lookup_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ struct TALER_MERCHANTDB_TemplateDetails *td);
/**
* Delete information about a template.
@@ -3830,9 +3951,9 @@ struct TALER_MERCHANTDB_Plugin
* if template unknown.
*/
enum GNUNET_DB_QueryStatus
- (*delete_template)(void *cls,
- const char *instance_id,
- const char *template_id);
+ (*delete_template)(void *cls,
+ const char *instance_id,
+ const char *template_id);
/**
@@ -3846,11 +3967,11 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_template)(void *cls,
- const char *instance_id,
- const char *template_id,
- uint64_t otp_serial_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td);
+ (*insert_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ uint64_t otp_serial_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
/**
@@ -3865,10 +3986,10 @@ struct TALER_MERCHANTDB_Plugin
* does not yet exist.
*/
enum GNUNET_DB_QueryStatus
- (*update_template)(void *cls,
- const char *instance_id,
- const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td);
+ (*update_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
/**
@@ -3881,9 +4002,9 @@ struct TALER_MERCHANTDB_Plugin
* if template unknown.
*/
enum GNUNET_DB_QueryStatus
- (*delete_otp)(void *cls,
- const char *instance_id,
- const char *otp_id);
+ (*delete_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id);
/**
* Insert details about a particular OTP device.
@@ -3895,10 +4016,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_otp)(void *cls,
- const char *instance_id,
- const char *otp_id,
- const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+ (*insert_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
/**
@@ -3913,10 +4034,10 @@ struct TALER_MERCHANTDB_Plugin
* does not yet exist.
*/
enum GNUNET_DB_QueryStatus
- (*update_otp)(void *cls,
- const char *instance_id,
- const char *otp_id,
- const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+ (*update_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ const struct TALER_MERCHANTDB_OtpDeviceDetails *td);
/**
* Lookup all of the OTP devices the given instance has configured.
@@ -3928,10 +4049,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_otp_devices)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_OtpDeviceCallback cb,
- void *cb_cls);
+ (*lookup_otp_devices)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_OtpDeviceCallback cb,
+ void *cb_cls);
/**
@@ -3945,10 +4066,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_otp)(void *cls,
- const char *instance_id,
- const char *otp_id,
- struct TALER_MERCHANTDB_OtpDeviceDetails *td);
+ (*select_otp)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ struct TALER_MERCHANTDB_OtpDeviceDetails *td);
/**
@@ -3960,10 +4081,10 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] serial set to the OTP device serial number * @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_otp_serial)(void *cls,
- const char *instance_id,
- const char *otp_id,
- uint64_t *serial);
+ (*select_otp_serial)(void *cls,
+ const char *instance_id,
+ const char *otp_id,
+ uint64_t *serial);
/**
* Delete information about a measurement unit.
@@ -3977,12 +4098,12 @@ struct TALER_MERCHANTDB_Plugin
* @return DB status code
*/
enum GNUNET_DB_QueryStatus
- (*delete_unit)(void *cls,
- const char *instance_id,
- const char *unit_id,
- bool *no_instance,
- bool *no_unit,
- bool *builtin_conflict);
+ (*delete_unit)(void *cls,
+ const char *instance_id,
+ const char *unit_id,
+ bool *no_instance,
+ bool *no_unit,
+ bool *builtin_conflict);
/**
* Insert a measurement unit definition.
@@ -3996,12 +4117,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_unit)(void *cls,
- const char *instance_id,
- const struct TALER_MERCHANTDB_UnitDetails *ud,
- bool *no_instance,
- bool *conflict,
- uint64_t *unit_serial);
+ (*insert_unit)(void *cls,
+ const char *instance_id,
+ const struct TALER_MERCHANTDB_UnitDetails *ud,
+ bool *no_instance,
+ bool *conflict,
+ uint64_t *unit_serial);
/**
* Update a measurement unit definition.
@@ -4022,19 +4143,19 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_unit)(void *cls,
- const char *instance_id,
- const char *unit_id,
- const char *unit_name_long,
- const json_t *unit_name_long_i18n,
- const char *unit_name_short,
- const json_t *unit_name_short_i18n,
- const bool *unit_allow_fraction,
- const uint32_t *unit_precision_level,
- const bool *unit_active,
- bool *no_instance,
- bool *no_unit,
- bool *builtin_conflict);
+ (*update_unit)(void *cls,
+ const char *instance_id,
+ const char *unit_id,
+ const char *unit_name_long,
+ const json_t *unit_name_long_i18n,
+ const char *unit_name_short,
+ const json_t *unit_name_short_i18n,
+ const bool *unit_allow_fraction,
+ const uint32_t *unit_precision_level,
+ const bool *unit_active,
+ bool *no_instance,
+ bool *no_unit,
+ bool *builtin_conflict);
/**
* Lookup all measurement units of an instance.
@@ -4046,12 +4167,30 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_units)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_UnitsCallback cb,
- void *cb_cls);
+ (*lookup_units)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_UnitsCallback cb,
+ void *cb_cls);
/**
+ * Lookup custom measurement units by name.
+ *
+ * @param cls closure
+ * @param instance_id instance to fetch units for
+ * @param units array of unit identifiers
+ * @param num_units length of @a units
+ * @param cb function to call per unit
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_custom_units_by_names)(void *cls,
+ const char *instance_id,
+ const char *const *units,
+ size_t num_units,
+ TALER_MERCHANTDB_UnitsCallback cb,
+ void *cb_cls);
+ /**
* Lookup a single measurement unit.
*
* @param cls closure
@@ -4061,10 +4200,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_unit)(void *cls,
- const char *instance_id,
- const char *unit_id,
- struct TALER_MERCHANTDB_UnitDetails *ud);
+ (*select_unit)(void *cls,
+ const char *instance_id,
+ const char *unit_id,
+ struct TALER_MERCHANTDB_UnitDetails *ud);
/**
@@ -4077,9 +4216,9 @@ struct TALER_MERCHANTDB_Plugin
* if template unknown.
*/
enum GNUNET_DB_QueryStatus
- (*delete_category)(void *cls,
- const char *instance_id,
- uint64_t category_id);
+ (*delete_category)(void *cls,
+ const char *instance_id,
+ uint64_t category_id);
/**
* Insert new product category.
@@ -4092,11 +4231,11 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_category)(void *cls,
- const char *instance_id,
- const char *category_name,
- const json_t *category_name_i18n,
- uint64_t *category_id);
+ (*insert_category)(void *cls,
+ const char *instance_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t *category_id);
/**
@@ -4111,11 +4250,11 @@ struct TALER_MERCHANTDB_Plugin
* does not yet exist.
*/
enum GNUNET_DB_QueryStatus
- (*update_category)(void *cls,
- const char *instance_id,
- uint64_t category_id,
- const char *category_name,
- const json_t *category_name_i18n);
+ (*update_category)(void *cls,
+ const char *instance_id,
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n);
/**
* Lookup all of the product categories the given instance has configured.
@@ -4127,10 +4266,29 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_categories)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_CategoriesCallback cb,
- void *cb_cls);
+ (*lookup_categories)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls);
+
+ /**
+ * Lookup product categories by ID.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup categories for
+ * @param category_ids array of category IDs
+ * @param num_category_ids length of @a category_ids
+ * @param cb function to call on all categories found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_categories_by_ids)(void *cls,
+ const char *instance_id,
+ const uint64_t *category_ids,
+ size_t num_category_ids,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls);
/**
@@ -4146,12 +4304,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_category)(void *cls,
- const char *instance_id,
- uint64_t category_id,
- struct TALER_MERCHANTDB_CategoryDetails *cd,
- size_t *num_products,
- char **products);
+ (*select_category)(void *cls,
+ const char *instance_id,
+ uint64_t category_id,
+ struct TALER_MERCHANTDB_CategoryDetails *cd,
+ size_t *num_products,
+ char **products);
/**
@@ -4165,11 +4323,11 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_category_by_name)(void *cls,
- const char *instance_id,
- const char *category_name,
- json_t **name_i18n,
- uint64_t *category_id);
+ (*select_category_by_name)(void *cls,
+ const char *instance_id,
+ const char *category_name,
+ json_t **name_i18n,
+ uint64_t *category_id);
/**
@@ -4182,10 +4340,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_webhooks)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_WebhooksCallback cb,
- void *cb_cls);
+ (*lookup_webhooks)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_WebhooksCallback cb,
+ void *cb_cls);
/**
@@ -4199,10 +4357,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_webhook)(void *cls,
- const char *instance_id,
- const char *webhook_id,
- struct TALER_MERCHANTDB_WebhookDetails *wb);
+ (*lookup_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ struct TALER_MERCHANTDB_WebhookDetails *wb);
/**
* Delete information about a webhook.
@@ -4214,9 +4372,9 @@ struct TALER_MERCHANTDB_Plugin
* if webhook unknown.
*/
enum GNUNET_DB_QueryStatus
- (*delete_webhook)(void *cls,
- const char *instance_id,
- const char *webhook_id);
+ (*delete_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id);
/**
@@ -4229,10 +4387,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_webhook)(void *cls,
- const char *instance_id,
- const char *webhook_id,
- const struct TALER_MERCHANTDB_WebhookDetails *wb);
+ (*insert_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
/**
@@ -4247,10 +4405,10 @@ struct TALER_MERCHANTDB_Plugin
* does not yet exist.
*/
enum GNUNET_DB_QueryStatus
- (*update_webhook)(void *cls,
- const char *instance_id,
- const char *webhook_id,
- const struct TALER_MERCHANTDB_WebhookDetails *wb);
+ (*update_webhook)(void *cls,
+ const char *instance_id,
+ const char *webhook_id,
+ const struct TALER_MERCHANTDB_WebhookDetails *wb);
/**
* Lookup webhook by event
@@ -4263,11 +4421,11 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_webhook_by_event)(void *cls,
- const char *instance_id,
- const char *event_type,
- TALER_MERCHANTDB_WebhookDetailCallback cb,
- void *cb_cls);
+ (*lookup_webhook_by_event)(void *cls,
+ const char *instance_id,
+ const char *event_type,
+ TALER_MERCHANTDB_WebhookDetailCallback cb,
+ void *cb_cls);
/**
* Insert webhook in the pending webhook.
@@ -4282,13 +4440,13 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_pending_webhook)(void *cls,
- const char *instance_id,
- uint64_t webhook_serial,
- const char *url,
- const char *http_method,
- const char *header,
- const char *body);
+ (*insert_pending_webhook)(void *cls,
+ const char *instance_id,
+ uint64_t webhook_serial,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body);
/**
* Lookup the webhook that need to be send in priority. These webhooks are not successfully
* send.
@@ -4299,9 +4457,9 @@ struct TALER_MERCHANTDB_Plugin
*/
// WHERE next_attempt <= now ORDER BY next_attempt ASC
enum GNUNET_DB_QueryStatus
- (*lookup_pending_webhooks)(void *cls,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls);
+ (*lookup_pending_webhooks)(void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
/**
* Lookup future webhook in the pending webhook that need to be send.
@@ -4313,9 +4471,9 @@ struct TALER_MERCHANTDB_Plugin
*/
// ORDER BY next_attempt ASC LIMIT 1
enum GNUNET_DB_QueryStatus
- (*lookup_future_webhook)(void *cls,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls);
+ (*lookup_future_webhook)(void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
/**
* Lookup all the webhooks in the pending webhook.
@@ -4330,12 +4488,12 @@ struct TALER_MERCHANTDB_Plugin
*/
// WHERE webhook_pending_serial > min_row ORDER BY webhook_pending_serial ASC LIMIT max_results
enum GNUNET_DB_QueryStatus
- (*lookup_all_webhooks)(void *cls,
- const char *instance_id,
- uint64_t min_row,
- uint32_t max_results,
- TALER_MERCHANTDB_PendingWebhooksCallback cb,
- void *cb_cls);
+ (*lookup_all_webhooks)(void *cls,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
/**
@@ -4347,9 +4505,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_pending_webhook)(void *cls,
- uint64_t webhook_pending_serial,
- struct GNUNET_TIME_Absolute next_attempt);
+ (*update_pending_webhook)(void *cls,
+ uint64_t webhook_pending_serial,
+ struct GNUNET_TIME_Absolute next_attempt);
// maybe add: http status of failure?
@@ -4362,8 +4520,8 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_pending_webhook)(void *cls,
- uint64_t webhook_pending_serial);
+ (*delete_pending_webhook)(void *cls,
+ uint64_t webhook_pending_serial);
/**
@@ -4376,10 +4534,10 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*select_exchange_keys)(void *cls,
- const char *exchange_url,
- struct GNUNET_TIME_Absolute *first_retry,
- struct TALER_EXCHANGE_Keys **keys);
+ (*select_exchange_keys)(void *cls,
+ const char *exchange_url,
+ struct GNUNET_TIME_Absolute *first_retry,
+ struct TALER_EXCHANGE_Keys **keys);
/**
@@ -4391,9 +4549,9 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*insert_exchange_keys)(void *cls,
- const struct TALER_EXCHANGE_Keys *keys,
- struct GNUNET_TIME_Absolute first_retry);
+ (*insert_exchange_keys)(void *cls,
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct GNUNET_TIME_Absolute first_retry);
/**
@@ -4406,10 +4564,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_token_families)(void *cls,
- const char *instance_id,
- TALER_MERCHANTDB_TokenFamiliesCallback cb,
- void *cb_cls);
+ (*lookup_token_families)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_TokenFamiliesCallback cb,
+ void *cb_cls);
/**
* Lookup details about a particular token family.
@@ -4422,10 +4580,10 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_token_family)(void *cls,
- const char *instance_id,
- const char *token_family_slug,
- struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+ (*lookup_token_family)(void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct TALER_MERCHANTDB_TokenFamilyDetails *details);
/**
* Delete information about a token family.
@@ -4436,9 +4594,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_token_family)(void *cls,
- const char *instance_id,
- const char *token_family_slug);
+ (*delete_token_family)(void *cls,
+ const char *instance_id,
+ const char *token_family_slug);
/**
* Update details about a particular token family.
@@ -4451,7 +4609,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_token_family)(
+ (*update_token_family)(
void *cls,
const char *instance_id,
const char *token_family_slug,
@@ -4468,7 +4626,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_token_family)(
+ (*insert_token_family)(
void *cls,
const char *instance_id,
const char *token_family_slug,
@@ -4488,7 +4646,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_token_family_key)(
+ (*lookup_token_family_key)(
void *cls,
const char *instance_id,
const char *token_family_slug,
@@ -4510,7 +4668,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_token_family_keys)(
+ (*lookup_token_family_keys)(
void *cls,
const char *instance_id,
const char *token_family_slug,
@@ -4535,7 +4693,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_token_family_key)(
+ (*insert_token_family_key)(
void *cls,
const char *merchant_id,
const char *token_family_slug,
@@ -4557,7 +4715,7 @@ struct TALER_MERCHANTDB_Plugin
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*lookup_pending_deposits)(
+ (*lookup_pending_deposits)(
void *cls,
const char *exchange_url,
uint64_t limit,
@@ -4580,7 +4738,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_deposit_confirmation_status)(
+ (*update_deposit_confirmation_status)(
void *cls,
uint64_t deposit_serial,
bool retry_needed,
@@ -4599,7 +4757,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_donau_instance_receipts_amount)(
+ (*update_donau_instance_receipts_amount)(
void *cls,
uint64_t *donau_instances_serial,
const struct TALER_Amount *new_amount
@@ -4626,7 +4784,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_mfa_challenge)(
+ (*lookup_mfa_challenge)(
void *cls,
uint64_t challenge_id,
const struct TALER_MERCHANT_MFA_BodyHash *h_body,
@@ -4656,7 +4814,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*solve_mfa_challenge)(
+ (*solve_mfa_challenge)(
void *cls,
uint64_t challenge_id,
const struct TALER_MERCHANT_MFA_BodyHash *h_body,
@@ -4680,7 +4838,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_mfa_challenge)(
+ (*update_mfa_challenge)(
void *cls,
uint64_t challenge_id,
const char *code,
@@ -4707,7 +4865,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*create_mfa_challenge)(
+ (*create_mfa_challenge)(
void *cls,
enum TALER_MERCHANT_MFA_CriticalOperation op,
const struct TALER_MERCHANT_MFA_BodyHash *h_body,
@@ -4730,7 +4888,7 @@ struct TALER_MERCHANTDB_Plugin
* @param charity_id charity ID of the Donau instance
*/
enum GNUNET_DB_QueryStatus
- (*insert_donau_instance)(
+ (*insert_donau_instance)(
void *cls,
const char *donau_url,
const struct DONAU_Charity *charity,
@@ -4747,7 +4905,7 @@ struct TALER_MERCHANTDB_Plugin
* @param charity_id charity ID of the Donau instance
*/
enum GNUNET_DB_QueryStatus
- (*check_donau_instance)(
+ (*check_donau_instance)(
void *cls,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *donau_url,
@@ -4763,7 +4921,7 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] charity_id set to the charity ID of the Donau instance
*/
enum GNUNET_DB_QueryStatus
- (*select_donau_instance_by_serial)(
+ (*select_donau_instance_by_serial)(
void *cls,
uint64_t serial,
char **donau_url,
@@ -4780,7 +4938,7 @@ struct TALER_MERCHANTDB_Plugin
* @param cb_cls closure for @a cb
*/
enum GNUNET_DB_QueryStatus
- (*select_donau_instances)(
+ (*select_donau_instances)(
void *cls,
const char *id,
TALER_MERCHANTDB_DonauInstanceCallback cb,
@@ -4796,7 +4954,7 @@ struct TALER_MERCHANTDB_Plugin
* @param cb_cls closure for @a cb
*/
enum GNUNET_DB_QueryStatus
- (*select_all_donau_instances)(
+ (*select_all_donau_instances)(
void *cls,
TALER_MERCHANTDB_DonauInstanceCallback cb,
void *cb_cls);
@@ -4811,7 +4969,7 @@ struct TALER_MERCHANTDB_Plugin
* @param cb_cls closure for @a cb
*/
enum GNUNET_DB_QueryStatus
- (*select_donau_instances_filtered)(
+ (*select_donau_instances_filtered)(
void *cls,
const char *currency,
TALER_MERCHANTDB_DonauInstanceFilteredCallback cb,
@@ -4825,7 +4983,7 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] keys set to the Donau keys on success
*/
enum GNUNET_DB_QueryStatus
- (*lookup_donau_keys)(
+ (*lookup_donau_keys)(
void *cls,
const char *donau_url,
struct GNUNET_TIME_Absolute *first_retry,
@@ -4847,7 +5005,7 @@ struct TALER_MERCHANTDB_Plugin
* Donau keys
*/
enum GNUNET_DB_QueryStatus
- (*lookup_order_charity)(
+ (*lookup_order_charity)(
void *cls,
const char *instance_id,
const char *donau_url,
@@ -4865,7 +5023,7 @@ struct TALER_MERCHANTDB_Plugin
* @param keys Donau keys to insert or update
*/
enum GNUNET_DB_QueryStatus
- (*upsert_donau_keys)(
+ (*upsert_donau_keys)(
void *cls,
const struct DONAU_Keys *keys,
struct GNUNET_TIME_Absolute first_retry);
@@ -4879,7 +5037,7 @@ struct TALER_MERCHANTDB_Plugin
* @param charity_id charity ID of the Donau instance
*/
enum GNUNET_DB_QueryStatus
- (*update_donau_instance)(
+ (*update_donau_instance)(
void *cls,
const char *donau_url,
const struct DONAU_Charity *charity,
@@ -4894,7 +5052,7 @@ struct TALER_MERCHANTDB_Plugin
* @param charity_id charity ID of the Donau instance to delete
*/
enum GNUNET_DB_QueryStatus
- (*delete_donau_instance)(
+ (*delete_donau_instance)(
void *cls,
const char *id,
uint64_t charity_id);
@@ -4910,7 +5068,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_statistics_amount_by_bucket)(
+ (*lookup_statistics_amount_by_bucket)(
void *cls,
const char *instance_id,
const char *slug,
@@ -4931,7 +5089,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_statistics_amount_by_bucket2)(
+ (*lookup_statistics_amount_by_bucket2)(
void *cls,
const char *instance_id,
const char *slug,
@@ -4952,7 +5110,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_statistics_counter_by_bucket)(
+ (*lookup_statistics_counter_by_bucket)(
void *cls,
const char *instance_id,
const char *slug,
@@ -4972,7 +5130,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_statistics_counter_by_bucket2)(
+ (*lookup_statistics_counter_by_bucket2)(
void *cls,
const char *instance_id,
const char *prefix,
@@ -4993,7 +5151,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_statistics_amount_by_interval)(
+ (*lookup_statistics_amount_by_interval)(
void *cls,
const char *instance_id,
const char *slug,
@@ -5011,7 +5169,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_statistics_counter_by_interval)(
+ (*lookup_statistics_counter_by_interval)(
void *cls,
const char *instance_id,
const char *slug,
@@ -5032,12 +5190,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_money_pots)(void *cls,
- const char *instance_id,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANTDB_MoneyPotsCallback cb,
- void *cb_cls);
+ (*select_money_pots)(void *cls,
+ const char *instance_id,
+ int64_t limit,
+ uint64_t offset,
+ TALER_MERCHANTDB_MoneyPotsCallback cb,
+ void *cb_cls);
/**
@@ -5056,11 +5214,11 @@ struct TALER_MERCHANTDB_Plugin
* @a pot_missing was initialized to a missing pot
*/
enum GNUNET_DB_QueryStatus
- (*check_money_pots)(void *cls,
- const char *instance_id,
- unsigned int pots_len,
- uint64_t pots[static pots_len],
- uint64_t *pot_missing);
+ (*check_money_pots)(void *cls,
+ const char *instance_id,
+ unsigned int pots_len,
+ uint64_t pots[static pots_len],
+ uint64_t *pot_missing);
/**
* Lookup details about a particular money pot.
@@ -5078,13 +5236,13 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_money_pot)(void *cls,
- const char *instance_id,
- uint64_t money_pot_id,
- char **name,
- char **description,
- size_t *pot_total_len,
- struct TALER_Amount **pot_totals);
+ (*select_money_pot)(void *cls,
+ const char *instance_id,
+ uint64_t money_pot_id,
+ char **name,
+ char **description,
+ size_t *pot_total_len,
+ struct TALER_Amount **pot_totals);
/**
* Delete information about a money pot.
@@ -5095,9 +5253,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_money_pot)(void *cls,
- const char *instance_id,
- uint64_t money_pot_id);
+ (*delete_money_pot)(void *cls,
+ const char *instance_id,
+ uint64_t money_pot_id);
/**
* Update details about a particular money pot.
@@ -5118,7 +5276,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_money_pot)(
+ (*update_money_pot)(
void *cls,
const char *instance_id,
uint64_t money_pot_id,
@@ -5145,7 +5303,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*increment_money_pots)(
+ (*increment_money_pots)(
void *cls,
const char *instance_id,
size_t money_pots_len,
@@ -5165,7 +5323,7 @@ struct TALER_MERCHANTDB_Plugin
* on conflict (@a name already in use at @a instance_id).
*/
enum GNUNET_DB_QueryStatus
- (*insert_money_pot)(
+ (*insert_money_pot)(
void *cls,
const char *instance_id,
const char *name,
@@ -5187,12 +5345,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_reports)(void *cls,
- const char *instance_id,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANTDB_ReportsCallback cb,
- void *cb_cls);
+ (*select_reports)(void *cls,
+ const char *instance_id,
+ int64_t limit,
+ uint64_t offset,
+ TALER_MERCHANTDB_ReportsCallback cb,
+ void *cb_cls);
/**
@@ -5204,9 +5362,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_reports_pending)(void *cls,
- TALER_MERCHANTDB_ReportsPendingCallback cb,
- void *cb_cls);
+ (*lookup_reports_pending)(void *cls,
+ TALER_MERCHANTDB_ReportsPendingCallback cb,
+ void *cb_cls);
/**
* Lookup details about a particular report.
@@ -5230,19 +5388,19 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_report)(void *cls,
- const char *instance_id,
- uint64_t report_id,
- char **report_program_section,
- char **report_description,
- char **mime_type,
- char **data_source,
- char **target_address,
- struct GNUNET_TIME_Relative *frequency,
- struct GNUNET_TIME_Relative *frequency_shift,
- struct GNUNET_TIME_Absolute *next_transmission,
- enum TALER_ErrorCode *last_error_code,
- char **last_error_detail);
+ (*select_report)(void *cls,
+ const char *instance_id,
+ uint64_t report_id,
+ char **report_program_section,
+ char **report_description,
+ char **mime_type,
+ char **data_source,
+ char **target_address,
+ struct GNUNET_TIME_Relative *frequency,
+ struct GNUNET_TIME_Relative *frequency_shift,
+ struct GNUNET_TIME_Absolute *next_transmission,
+ enum TALER_ErrorCode *last_error_code,
+ char **last_error_detail);
/**
* Check that a particular report is scheduled under the
@@ -5259,12 +5417,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*check_report)(void *cls,
- uint64_t report_id,
- const struct TALER_MERCHANT_ReportToken *report_token,
- const char *mime_type,
- char **instance_id,
- char **data_source);
+ (*check_report)(void *cls,
+ uint64_t report_id,
+ const struct TALER_MERCHANT_ReportToken *report_token,
+ const char *mime_type,
+ char **instance_id,
+ char **data_source);
/**
@@ -5276,9 +5434,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_report)(void *cls,
- const char *instance_id,
- uint64_t report_id);
+ (*delete_report)(void *cls,
+ const char *instance_id,
+ uint64_t report_id);
/**
* Update transmission status about a particular report.
@@ -5292,7 +5450,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_report_status)(
+ (*update_report_status)(
void *cls,
const char *instance_id,
uint64_t report_id,
@@ -5320,7 +5478,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_report)(
+ (*update_report)(
void *cls,
const char *instance_id,
uint64_t report_id,
@@ -5352,7 +5510,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*insert_report)(
+ (*insert_report)(
void *cls,
const char *instance_id,
const char *report_program_section,
@@ -5380,12 +5538,12 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*select_product_groups)(void *cls,
- const char *instance_id,
- int64_t limit,
- uint64_t offset,
- TALER_MERCHANTDB_ProductGroupsCallback cb,
- void *cb_cls);
+ (*select_product_groups)(void *cls,
+ const char *instance_id,
+ int64_t limit,
+ uint64_t offset,
+ TALER_MERCHANTDB_ProductGroupsCallback cb,
+ void *cb_cls);
/**
* Delete information about a product group.
@@ -5396,9 +5554,9 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*delete_product_group)(void *cls,
- const char *instance_id,
- uint64_t product_group_id);
+ (*delete_product_group)(void *cls,
+ const char *instance_id,
+ uint64_t product_group_id);
/**
* Update details about a particular product group.
@@ -5412,7 +5570,7 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*update_product_group)(
+ (*update_product_group)(
void *cls,
const char *instance_id,
uint64_t product_group_id,
@@ -5433,14 +5591,13 @@ struct TALER_MERCHANTDB_Plugin
* on conflict (@a name already in use at @a instance_id).
*/
enum GNUNET_DB_QueryStatus
- (*insert_product_group)(
+ (*insert_product_group)(
void *cls,
const char *instance_id,
const char *name,
const char *description,
uint64_t *product_group_id);
-
};
#endif
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
@@ -67,6 +67,7 @@ libtalermerchant_la_SOURCES = \
merchant_api_post_order_pay.c \
merchant_api_post_order_refund.c \
merchant_api_post_otp_devices.c \
+ merchant_api_post_categories.c \
merchant_api_post_products.c \
merchant_api_post_units.c \
merchant_api_post_transfers.c \
diff --git a/src/lib/merchant_api_post_categories.c b/src/lib/merchant_api_post_categories.c
@@ -0,0 +1,223 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_categories.c
+ * @brief Implementation of POST /private/categories
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#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/categories operation.
+ */
+struct TALER_MERCHANT_CategoriesPostHandle
+{
+ /**
+ * Fully qualified request URL.
+ */
+ char *url;
+
+ /**
+ * CURL job handle.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Callback to invoke with the result.
+ */
+ TALER_MERCHANT_CategoriesPostCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Helper keeping POST body and headers alive.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+};
+
+/**
+ * Called when the HTTP transfer finishes.
+ *
+ * @param cls operation handle
+ * @param response_code HTTP status (0 on network / parsing failures)
+ * @param response parsed JSON reply (NULL if unavailable)
+ */
+static void
+handle_post_categories_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_CategoriesPostHandle *cph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_CategoriesPostResponse cpr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ cph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /private/categories completed with status %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *err_name;
+ unsigned int err_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("category_id",
+ &cpr.category_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid response for field %s\n",
+ err_name);
+ cpr.hr.http_status = 0;
+ cpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ }
+ break;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ cpr.hr.ec = TALER_JSON_get_error_code (json);
+ cpr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case 0:
+ cpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &cpr.hr);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response %u/%d for POST /private/categories\n",
+ (unsigned int) response_code,
+ (int) cpr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ cph->cb (cph->cb_cls,
+ &cpr);
+ TALER_MERCHANT_categories_post_cancel (cph);
+}
+
+
+struct TALER_MERCHANT_CategoriesPostHandle *
+TALER_MERCHANT_categories_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *name,
+ const json_t *name_i18n,
+ TALER_MERCHANT_CategoriesPostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_CategoriesPostHandle *cph;
+ json_t *req_obj;
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ name),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("name_i18n",
+ (json_t *) name_i18n)));
+ cph = GNUNET_new (struct TALER_MERCHANT_CategoriesPostHandle);
+ cph->ctx = ctx;
+ cph->cb = cb;
+ cph->cb_cls = cb_cls;
+ cph->url = TALER_url_join (backend_url,
+ "private/categories",
+ NULL);
+ if (NULL == cph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to build /private/categories URL\n");
+ json_decref (req_obj);
+ GNUNET_free (cph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (cph->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&cph->post_ctx,
+ eh,
+ req_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (req_obj);
+ GNUNET_free (cph->url);
+ GNUNET_free (cph);
+ return NULL;
+ }
+ json_decref (req_obj);
+ cph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ cph->post_ctx.headers,
+ &handle_post_categories_finished,
+ cph);
+ }
+ return cph;
+}
+
+
+void
+TALER_MERCHANT_categories_post_cancel (
+ struct TALER_MERCHANT_CategoriesPostHandle *cph)
+{
+ if (NULL != cph->job)
+ {
+ GNUNET_CURL_job_cancel (cph->job);
+ cph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&cph->post_ctx);
+ GNUNET_free (cph->url);
+ GNUNET_free (cph);
+}
+
+
+/* end of merchant_api_post_categories.c */
diff --git a/src/lib/merchant_api_post_templates.c b/src/lib/merchant_api_post_templates.c
@@ -32,6 +32,178 @@
#include "merchant_api_common.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_curl_lib.h>
+#include <taler/taler_merchant_util.h>
+
+
+/**
+ * Validate a fixed-order template contract.
+ *
+ * @param template_contract JSON template contract
+ * @param summary summary pointer
+ * @param amount amount storage
+ * @param minimum_age minimum age storage
+ * @param pay_duration pay duration storage
+ * @return true if valid
+ */
+static bool
+validate_template_contract_fixed (const json_t *template_contract,
+ const char **summary,
+ struct TALER_Amount *amount,
+ uint32_t *minimum_age,
+ struct GNUNET_TIME_Relative *pay_duration)
+{
+ 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;
+}
+
+
+/**
+ * Validate an inventory-cart template contract.
+ *
+ * @param template_contract JSON template contract
+ * @param summary summary pointer
+ * @param pay_duration pay duration storage
+ * @return true if valid
+ */
+static bool
+validate_template_contract_inventory (const json_t *template_contract,
+ const char **summary,
+ struct GNUNET_TIME_Relative *pay_duration)
+{
+ bool selected_all = false;
+ bool choose_one = false;
+ bool request_tip = false;
+ const json_t *selected_categories = NULL;
+ const json_t *selected_products = NULL;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("summary",
+ summary),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("request_tip",
+ &request_tip),
+ NULL),
+ GNUNET_JSON_spec_relative_time ("pay_duration",
+ pay_duration),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("selected_all",
+ &selected_all),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_categories",
+ &selected_categories),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("selected_products",
+ &selected_products),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("choose_one",
+ &choose_one),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *ename;
+ unsigned int eline;
+
+ (void) request_tip;
+ (void) selected_all;
+ (void) choose_one;
+ (void) selected_categories;
+ (void) selected_products;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (template_contract,
+ ispec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid template_contract for field %s\n",
+ ename);
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Validate a paivana template contract.
+ *
+ * @param template_contract JSON template contract
+ * @param summary summary pointer
+ * @param amount amount storage
+ * @param minimum_age minimum age storage
+ * @param pay_duration pay duration storage
+ * @return true if valid
+ */
+static bool
+validate_template_contract_paivana (const json_t *template_contract,
+ const char **summary,
+ struct TALER_Amount *amount,
+ uint32_t *minimum_age,
+ struct GNUNET_TIME_Relative *pay_duration)
+{
+ /* TODO: PAIVANA validate paivana-specific fields beyond presence. */
+ const char *paivana_id;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_string ("paivana_id",
+ &paivana_id),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *ename;
+ unsigned int eline;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (template_contract,
+ pspec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid paivana template_contract for field %s\n",
+ ename);
+ return false;
+ }
+
+ if (! validate_template_contract_fixed (template_contract,
+ summary,
+ amount,
+ minimum_age,
+ pay_duration))
+ return false;
+
+ (void) paivana_id;
+ return true;
+}
/**
@@ -160,31 +332,25 @@ handle_post_templates_finished (void *cls,
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),
+ const char *template_type = NULL;
+ enum TALER_MERCHANT_TemplateType template_type_enum;
+ struct GNUNET_JSON_Specification type_spec[] = {
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any ("amount",
- &amount),
+ GNUNET_JSON_spec_string ("template_type",
+ &template_type),
NULL),
- GNUNET_JSON_spec_uint32 ("minimum_age",
- &minimum_age),
- GNUNET_JSON_spec_relative_time ("pay_duration",
- &pay_duration),
GNUNET_JSON_spec_end ()
};
+ const char *summary;
+ struct TALER_Amount amount = { .value = 0};
+ uint32_t minimum_age = 0;
+ struct GNUNET_TIME_Relative pay_duration = { 0 };
const char *ename;
unsigned int eline;
if (GNUNET_OK !=
GNUNET_JSON_parse (template_contract,
- spec,
+ type_spec,
&ename,
&eline))
{
@@ -193,7 +359,39 @@ test_template_contract_valid (const json_t *template_contract)
ename);
return false;
}
- return true;
+
+ template_type_enum =
+ TALER_MERCHANT_template_type_from_string (template_type);
+ if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == template_type_enum)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid template_type '%s'\n",
+ template_type);
+ return false;
+ }
+
+ switch (template_type_enum)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ return validate_template_contract_inventory (template_contract,
+ &summary,
+ &pay_duration);
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ return validate_template_contract_fixed (template_contract,
+ &summary,
+ &amount,
+ &minimum_age,
+ &pay_duration);
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ return validate_template_contract_paivana (template_contract,
+ &summary,
+ &amount,
+ &minimum_age,
+ &pay_duration);
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+ return false;
}
diff --git a/src/lib/merchant_api_post_using_templates.c b/src/lib/merchant_api_post_using_templates.c
@@ -96,26 +96,28 @@ handle_post_using_templates_finished (void *cls,
}
-struct TALER_MERCHANT_UsingTemplatesPostHandle *
-TALER_MERCHANT_using_templates_post (
+/**
+ * Helper to submit a POST /templates/$ID request.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param template_id identifier of the template to use
+ * @param req_obj JSON request body (consumed)
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+static struct TALER_MERCHANT_UsingTemplatesPostHandle *
+using_templates_post_internal (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *template_id,
- const char *summary,
- const struct TALER_Amount *amount,
+ json_t *req_obj,
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;
@@ -159,6 +161,58 @@ TALER_MERCHANT_using_templates_post (
}
+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)
+{
+ 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)));
+ return using_templates_post_internal (ctx,
+ backend_url,
+ template_id,
+ req_obj,
+ cb,
+ cb_cls);
+}
+
+
+struct TALER_MERCHANT_UsingTemplatesPostHandle *
+TALER_MERCHANT_using_templates_post2 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *template_id,
+ const json_t *details,
+ TALER_MERCHANT_PostOrdersCallback cb,
+ void *cb_cls)
+{
+ if (NULL == details)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No request body provided for using templates.\n");
+ return NULL;
+ }
+ return using_templates_post_internal (ctx,
+ backend_url,
+ template_id,
+ (json_t *) details,
+ cb,
+ cb_cls);
+}
+
+
void
TALER_MERCHANT_using_templates_post_cancel (
struct TALER_MERCHANT_UsingTemplatesPostHandle *utph)
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
@@ -78,6 +78,7 @@ libtalermerchanttesting_la_SOURCES = \
testing_api_cmd_post_orders_paid.c \
testing_api_cmd_post_orders.c \
testing_api_cmd_post_otp_devices.c \
+ testing_api_cmd_post_categories.c \
testing_api_cmd_post_products.c \
testing_api_cmd_post_transfers.c \
testing_api_cmd_post_templates.c \
@@ -88,6 +89,7 @@ libtalermerchanttesting_la_SOURCES = \
testing_api_cmd_refund_order.c \
testing_api_cmd_tme.c \
testing_api_cmd_wallet_get_order.c \
+ testing_api_cmd_wallet_get_template.c \
testing_api_cmd_wallet_post_orders_refund.c \
testing_api_cmd_webhook.c \
testing_api_cmd_testserver.c \
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
@@ -160,6 +160,101 @@ static const struct TALER_TESTING_ProductUnitExpectations
};
/**
+ * Build an array of selected product IDs.
+ *
+ * @param first first product id
+ * @param second second product id or NULL
+ * @return JSON array of product ids
+ */
+static json_t *
+make_selected_products (const char *first,
+ const char *second)
+{
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ if (NULL != first)
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string (first)));
+ if (NULL != second)
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_string (second)));
+ return arr;
+}
+
+
+/**
+ * Build an array of selected category IDs.
+ *
+ * @param first first category id
+ * @param second second category id or 0
+ * @return JSON array of category ids
+ */
+static json_t *
+make_selected_categories (uint64_t first,
+ uint64_t second)
+{
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ if (0 != first)
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_integer (first)));
+ if (0 != second)
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ json_integer (second)));
+ return arr;
+}
+
+
+/**
+ * Build an inventory selection array.
+ *
+ * @param first_id first product id
+ * @param first_qty first quantity
+ * @param second_id second product id or NULL
+ * @param second_qty second quantity or NULL
+ * @return JSON array of inventory selections
+ */
+static json_t *
+make_inventory_selection (const char *first_id,
+ const char *first_qty,
+ const char *second_id,
+ const char *second_qty)
+{
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ if (NULL != first_id)
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("product_id",
+ first_id),
+ GNUNET_JSON_pack_string ("quantity",
+ first_qty))));
+ if (NULL != second_id)
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ arr,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("product_id",
+ second_id),
+ GNUNET_JSON_pack_string ("quantity",
+ second_qty))));
+ return arr;
+}
+
+
+/**
* Execute the taler-merchant-webhook command with
* our configuration file.
*
@@ -1799,6 +1894,348 @@ run (void *cls,
"EUR:4.99",
"EUR:4.99",
NULL),
+ cmd_transfer_to_exchange ("create-reserve-20y",
+ "EUR:10.02"),
+ cmd_exec_wirewatch ("wirewatch-20y"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-20y",
+ "EUR:10.02",
+ payer_payto,
+ exchange_payto,
+ "create-reserve-20y"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-yk",
+ "create-reserve-20y",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-ykb",
+ "create-reserve-20y",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status-20y",
+ "create-reserve-20y",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_products ("post-products-inv-1",
+ merchant_url,
+ "inv-product-1",
+ "Inventory Product One",
+ "EUR:1",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_products ("post-products-inv-2",
+ merchant_url,
+ "inv-product-2",
+ "Inventory Product Two",
+ "EUR:2",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_templates2 (
+ "post-templates-inv-one",
+ merchant_url,
+ "template-inv-one",
+ "inventory template one",
+ NULL,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("template_type",
+ "inventory-cart"),
+ GNUNET_JSON_pack_string ("summary",
+ "inventory template"),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES),
+ GNUNET_JSON_pack_array_steal (
+ "selected_products",
+ make_selected_products ("inv-product-1",
+ "inv-product-2")),
+ GNUNET_JSON_pack_bool ("choose_one",
+ true)),
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-one",
+ "post-templates-inv-one",
+ NULL,
+ merchant_url,
+ "inv-1",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:1"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-1",
+ "1.0",
+ NULL,
+ NULL))),
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_templates2 (
+ "post-templates-inv-multi",
+ merchant_url,
+ "template-inv-multi",
+ "inventory template multi",
+ NULL,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("template_type",
+ "inventory-cart"),
+ GNUNET_JSON_pack_string ("summary",
+ "inventory template multi"),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES),
+ GNUNET_JSON_pack_array_steal (
+ "selected_products",
+ make_selected_products ("inv-product-1",
+ "inv-product-2"))),
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-multi",
+ "post-templates-inv-multi",
+ NULL,
+ merchant_url,
+ "inv-2",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:3"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-1",
+ "1.0",
+ "inv-product-2",
+ "1.0"))),
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-one-empty",
+ "post-templates-inv-one",
+ NULL,
+ merchant_url,
+ "inv-1-empty",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:1"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ json_array ())),
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-one-two",
+ "post-templates-inv-one",
+ NULL,
+ merchant_url,
+ "inv-1-two",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:3"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-1",
+ "1.0",
+ "inv-product-2",
+ "1.0"))),
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-one-tip",
+ "post-templates-inv-one",
+ NULL,
+ merchant_url,
+ "inv-1-tip",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:1"),
+ GNUNET_JSON_pack_string ("tip",
+ "EUR:1"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-1",
+ "1.0",
+ NULL,
+ NULL))),
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-one-unknown",
+ "post-templates-inv-one",
+ NULL,
+ merchant_url,
+ "inv-1-unknown",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:1"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-404",
+ "1.0",
+ NULL,
+ NULL))),
+ MHD_HTTP_NOT_FOUND),
+ TALER_TESTING_cmd_merchant_post_templates2 (
+ "post-templates-inv-only",
+ merchant_url,
+ "template-inv-only",
+ "inventory template only",
+ NULL,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("template_type",
+ "inventory-cart"),
+ GNUNET_JSON_pack_string ("summary",
+ "inventory template only"),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES),
+ GNUNET_JSON_pack_array_steal (
+ "selected_products",
+ make_selected_products ("inv-product-1",
+ NULL))),
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-only-wrong",
+ "post-templates-inv-only",
+ NULL,
+ merchant_url,
+ "inv-only-wrong",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:2"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-2",
+ "1.0",
+ NULL,
+ NULL))),
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-multi-amount-mismatch",
+ "post-templates-inv-multi",
+ NULL,
+ merchant_url,
+ "inv-2-mismatch",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:4"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-1",
+ "1.0",
+ "inv-product-2",
+ "1.0"))),
+ MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_merchant_post_units ("post-unit-special",
+ merchant_url,
+ "special-unit",
+ "Special Unit",
+ "sunit",
+ true,
+ 1,
+ true,
+ json_pack ("{s:s}",
+ "en",
+ "Special Unit"),
+ json_pack ("{s:s}",
+ "en",
+ "sunit"),
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_categories ("post-category-special",
+ merchant_url,
+ "Category Special",
+ json_pack ("{s:s}",
+ "en",
+ "Category Special"),
+ 1,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_categories ("post-category-special-2",
+ merchant_url,
+ "Category Special Two",
+ json_pack ("{s:s}",
+ "en",
+ "Category Special Two"),
+ 2,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_products_with_categories (
+ "post-products-inv-unit-1",
+ merchant_url,
+ "inv-unit-product-1",
+ "Inventory Unit Product One",
+ "special-unit",
+ "EUR:5",
+ 1,
+ (const uint64_t[]) { 1 },
+ true,
+ 1,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_products (
+ "post-products-inv-unit-2",
+ merchant_url,
+ "inv-unit-product-2",
+ "Inventory Unit Product Two",
+ "EUR:1.2",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_products_with_categories (
+ "post-products-inv-unit-3",
+ merchant_url,
+ "inv-unit-product-3",
+ "Inventory Unit Product Three",
+ "test-unit",
+ "EUR:9",
+ 1,
+ (const uint64_t[]) { 2 },
+ false,
+ 0,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_post_templates2 (
+ "post-templates-inv-cat-prod-unit",
+ merchant_url,
+ "template-inv-cat-prod-unit",
+ "inventory template category+product unit",
+ NULL,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("template_type",
+ "inventory-cart"),
+ GNUNET_JSON_pack_string ("summary",
+ "inventory template category+product unit"),
+ GNUNET_JSON_pack_time_rel ("pay_duration",
+ GNUNET_TIME_UNIT_MINUTES),
+ GNUNET_JSON_pack_array_steal (
+ "selected_categories",
+ make_selected_categories (1,
+ 0)),
+ GNUNET_JSON_pack_array_steal (
+ "selected_products",
+ make_selected_products ("inv-unit-product-2",
+ "inv-unit-product-3"))),
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_wallet_get_template (
+ "wallet-get-template-inv-cat-prod-unit",
+ merchant_url,
+ "template-inv-cat-prod-unit",
+ 3,
+ "inv-unit-product-1",
+ "special-unit",
+ true,
+ 1,
+ json_pack ("{s:s}",
+ "en",
+ "sunit"),
+ "inv-unit-product-2",
+ "inv-unit-product-3",
+ 1,
+ 2,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-cat-prod-unit",
+ "post-templates-inv-cat-prod-unit",
+ NULL,
+ merchant_url,
+ "inv-cat-prod-unit",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:3.2"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-unit-product-1",
+ "0.4",
+ "inv-unit-product-2",
+ "1"))),
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_pay_order ("pay-inv-cat-prod-unit",
+ merchant_url,
+ MHD_HTTP_OK,
+ "using-templates-inv-cat-prod-unit",
+ "withdraw-coin-yk",
+ "EUR:3.2",
+ "EUR:3.2",
+ NULL),
TALER_TESTING_cmd_end ()
diff --git a/src/testing/testing_api_cmd_post_categories.c b/src/testing/testing_api_cmd_post_categories.c
@@ -0,0 +1,226 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_post_categories.c
+ * @brief command to test POST /private/categories
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * State of a "POST /private/categories" CMD.
+ */
+struct PostCategoriesState
+{
+ /**
+ * Handle for a "POST /private/categories" request.
+ */
+ struct TALER_MERCHANT_CategoriesPostHandle *cph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Category name.
+ */
+ const char *name;
+
+ /**
+ * Category name translations.
+ */
+ json_t *name_i18n;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Expected category id (0 to ignore).
+ */
+ uint64_t expected_category_id;
+
+ /**
+ * Category id returned by the backend.
+ */
+ uint64_t category_id;
+};
+
+/**
+ * Callback for POST /private/categories.
+ *
+ * @param cls closure
+ * @param cpr response details
+ */
+static void
+post_categories_cb (void *cls,
+ const struct TALER_MERCHANT_CategoriesPostResponse *cpr)
+{
+ struct PostCategoriesState *pcs = cls;
+
+ pcs->cph = NULL;
+ if (pcs->http_status != cpr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ cpr->hr.http_status,
+ (int) cpr->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (pcs->is));
+ TALER_TESTING_interpreter_fail (pcs->is);
+ return;
+ }
+ if (MHD_HTTP_OK == cpr->hr.http_status)
+ {
+ pcs->category_id = cpr->category_id;
+ if ( (0 != pcs->expected_category_id) &&
+ (pcs->expected_category_id != pcs->category_id) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected category_id %lu (expected %lu)\n",
+ pcs->category_id,
+ pcs->expected_category_id);
+ TALER_TESTING_interpreter_fail (pcs->is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (pcs->is);
+}
+
+
+/**
+ * Run the "POST /private/categories" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_categories_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostCategoriesState *pcs = cls;
+
+ pcs->is = is;
+ pcs->cph = TALER_MERCHANT_categories_post (
+ TALER_TESTING_interpreter_get_context (is),
+ pcs->merchant_url,
+ pcs->name,
+ pcs->name_i18n,
+ &post_categories_cb,
+ pcs);
+ GNUNET_assert (NULL != pcs->cph);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+post_categories_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PostCategoriesState *pcs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_category_id (&pcs->category_id),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Free the state of a "POST /private/categories" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_categories_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostCategoriesState *pcs = cls;
+
+ if (NULL != pcs->cph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /private/categories operation did not complete\n");
+ TALER_MERCHANT_categories_post_cancel (pcs->cph);
+ }
+ json_decref (pcs->name_i18n);
+ GNUNET_free (pcs);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_categories (
+ const char *label,
+ const char *merchant_url,
+ const char *name,
+ json_t *name_i18n,
+ uint64_t expected_category_id,
+ unsigned int http_status)
+{
+ struct PostCategoriesState *pcs;
+
+ GNUNET_assert ((NULL == name_i18n) ||
+ json_is_object (name_i18n));
+ pcs = GNUNET_new (struct PostCategoriesState);
+ pcs->merchant_url = merchant_url;
+ pcs->name = name;
+ pcs->name_i18n = name_i18n; /* ownership taken */
+ pcs->http_status = http_status;
+ pcs->expected_category_id = expected_category_id;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pcs,
+ .label = label,
+ .run = &post_categories_run,
+ .cleanup = &post_categories_cleanup,
+ .traits = &post_categories_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_post_categories.c */
diff --git a/src/testing/testing_api_cmd_post_products.c b/src/testing/testing_api_cmd_post_products.c
@@ -131,6 +131,16 @@ struct PostProductsState
struct GNUNET_TIME_Timestamp next_restock;
/**
+ * Categories array length.
+ */
+ unsigned int num_cats;
+
+ /**
+ * Categories array.
+ */
+ uint64_t *cats;
+
+ /**
* Expected HTTP response code.
*/
unsigned int http_status;
@@ -300,29 +310,57 @@ post_products_run (void *cls,
pis->address,
pis->next_restock,
pis->minimum_age,
- 0,
- NULL,
+ pis->num_cats,
+ pis->cats,
&post_products_cb,
pis);
}
else
{
- pis->iph = TALER_MERCHANT_products_post2 (
- TALER_TESTING_interpreter_get_context (is),
- pis->merchant_url,
- pis->product_id,
- pis->description,
- pis->description_i18n,
- pis->unit,
- &pis->price,
- pis->image,
- pis->taxes,
- pis->total_stock,
- pis->address,
- pis->next_restock,
- pis->minimum_age,
- &post_products_cb,
- pis);
+ if (0 < pis->num_cats)
+ {
+ pis->iph = TALER_MERCHANT_products_post4 (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->product_id,
+ pis->description,
+ pis->description_i18n,
+ pis->unit,
+ &pis->price,
+ 1,
+ pis->image,
+ pis->taxes,
+ pis->total_stock,
+ 0,
+ false,
+ NULL,
+ pis->address,
+ pis->next_restock,
+ pis->minimum_age,
+ pis->num_cats,
+ pis->cats,
+ &post_products_cb,
+ pis);
+ }
+ else
+ {
+ pis->iph = TALER_MERCHANT_products_post2 (
+ TALER_TESTING_interpreter_get_context (is),
+ pis->merchant_url,
+ pis->product_id,
+ pis->description,
+ pis->description_i18n,
+ pis->unit,
+ &pis->price,
+ pis->image,
+ pis->taxes,
+ pis->total_stock,
+ pis->address,
+ pis->next_restock,
+ pis->minimum_age,
+ &post_products_cb,
+ pis);
+ }
}
GNUNET_assert (NULL != pis->iph);
}
@@ -396,6 +434,7 @@ post_products_cleanup (void *cls,
GNUNET_free (pis->image);
json_decref (pis->taxes);
json_decref (pis->address);
+ GNUNET_free (pis->cats);
GNUNET_free (pis);
}
@@ -443,6 +482,8 @@ TALER_TESTING_cmd_merchant_post_products2 (
pis->minimum_age = minimum_age;
pis->address = address; /* ownership taken */
pis->next_restock = next_restock;
+ pis->num_cats = 0;
+ pis->cats = NULL;
post_products_update_unit_total_stock (pis);
{
struct TALER_TESTING_Command cmd = {
@@ -532,4 +573,55 @@ TALER_TESTING_cmd_merchant_post_products (
}
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_products_with_categories (
+ const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ const char *description,
+ const char *unit,
+ const char *price,
+ unsigned int num_cats,
+ const uint64_t *cats,
+ bool unit_allow_fraction,
+ uint32_t unit_precision_level,
+ unsigned int http_status)
+{
+ struct TALER_TESTING_Command cmd;
+
+ cmd = TALER_TESTING_cmd_merchant_post_products2 (
+ label,
+ merchant_url,
+ product_id,
+ description,
+ json_pack ("{s:s}", "en", description),
+ unit,
+ price,
+ "",
+ json_array (),
+ 4, /* total stock */
+ 0, /* minimum age */
+ json_pack ("{s:s}", "street", "my street"),
+ GNUNET_TIME_UNIT_ZERO_TS,
+ http_status);
+ {
+ struct PostProductsState *pis = cmd.cls;
+
+ pis->num_cats = num_cats;
+ if (0 < num_cats)
+ {
+ pis->cats = GNUNET_new_array (num_cats,
+ uint64_t);
+ for (unsigned int i = 0; i < num_cats; i++)
+ pis->cats[i] = cats[i];
+ }
+ pis->unit_allow_fraction = unit_allow_fraction;
+ pis->unit_precision_level = unit_precision_level;
+ if (unit_allow_fraction)
+ pis->use_fractional = true;
+ }
+ return cmd;
+}
+
+
/* end of testing_api_cmd_post_products.c */
diff --git a/src/testing/testing_api_cmd_post_using_templates.c b/src/testing/testing_api_cmd_post_using_templates.c
@@ -72,6 +72,11 @@ struct PostUsingTemplatesState
struct TALER_Amount amount;
/**
+ * Raw request body for inventory templates (if set).
+ */
+ json_t *details;
+
+ /**
* Label of a command that created the template we should use.
*/
const char *template_ref;
@@ -376,16 +381,29 @@ post_using_templates_run (void *cls,
&tis->otp_alg))
TALER_TESTING_FAIL (is);
}
- tis->iph = TALER_MERCHANT_using_templates_post (
- TALER_TESTING_interpreter_get_context (is),
- tis->merchant_url,
- template_id,
- tis->summary,
- TALER_amount_is_valid (&tis->amount)
- ? &tis->amount
- : NULL,
- &post_using_templates_cb,
- tis);
+ if (NULL != tis->details)
+ {
+ tis->iph = TALER_MERCHANT_using_templates_post2 (
+ TALER_TESTING_interpreter_get_context (is),
+ tis->merchant_url,
+ template_id,
+ tis->details,
+ &post_using_templates_cb,
+ tis);
+ }
+ else
+ {
+ tis->iph = TALER_MERCHANT_using_templates_post (
+ TALER_TESTING_interpreter_get_context (is),
+ tis->merchant_url,
+ template_id,
+ tis->summary,
+ TALER_amount_is_valid (&tis->amount)
+ ? &tis->amount
+ : NULL,
+ &post_using_templates_cb,
+ tis);
+ }
GNUNET_assert (NULL != tis->iph);
}
@@ -450,6 +468,7 @@ post_using_templates_cleanup (void *cls,
}
json_decref (tis->order_terms);
json_decref (tis->contract_terms);
+ json_decref (tis->details);
GNUNET_free (tis->order_id);
GNUNET_free (tis);
}
@@ -604,4 +623,39 @@ TALER_TESTING_cmd_merchant_post_using_templates (
}
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_using_templates2 (
+ const char *label,
+ const char *template_ref,
+ const char *otp_ref,
+ const char *merchant_url,
+ const char *using_template_id,
+ const json_t *details,
+ unsigned int http_status)
+{
+ struct PostUsingTemplatesState *tis;
+
+ tis = GNUNET_new (struct PostUsingTemplatesState);
+ tis->template_ref = template_ref;
+ tis->otp_ref = otp_ref;
+ tis->merchant_url = merchant_url;
+ tis->using_template_id = using_template_id;
+ tis->http_status = http_status;
+ tis->with_claim = true;
+ if (NULL != details)
+ tis->details = json_incref ((json_t *) details);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tis,
+ .label = label,
+ .run = &post_using_templates_run,
+ .cleanup = &post_using_templates_cleanup,
+ .traits = &post_using_templates_traits
+ };
+
+ return cmd;
+ }
+}
+
+
/* end of testing_api_cmd_post_using_templates.c */
diff --git a/src/testing/testing_api_cmd_wallet_get_template.c b/src/testing/testing_api_cmd_wallet_get_template.c
@@ -0,0 +1,449 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_wallet_get_template.c
+ * @brief command to test GET /templates/$ID (wallet)
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <jansson.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * State of a "GET template" wallet CMD.
+ */
+struct WalletGetTemplateState
+{
+ /**
+ * Handle for a "GET template" request.
+ */
+ struct TALER_MERCHANT_WalletTemplateGetHandle *igh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * ID of the template to run GET for.
+ */
+ const char *template_id;
+
+ /**
+ * Expected product count (0 to ignore).
+ */
+ size_t expected_products_len;
+
+ /**
+ * Product id to verify unit info for (optional).
+ */
+ const char *expected_product_id;
+
+ /**
+ * Expected unit for @e expected_product_id.
+ */
+ const char *expected_unit;
+
+ /**
+ * Expected allow_fraction for @e expected_product_id.
+ */
+ bool expected_unit_allow_fraction;
+
+ /**
+ * Expected precision for @e expected_product_id.
+ */
+ uint32_t expected_unit_precision_level;
+
+ /**
+ * Expected unit name short i18n for @e expected_unit.
+ */
+ json_t *expected_unit_name_short_i18n;
+
+ /**
+ * Optional second product id expected to be present.
+ */
+ const char *expected_product_id2;
+
+ /**
+ * Optional third product id expected to be present.
+ */
+ const char *expected_product_id3;
+
+ /**
+ * Expected category id 1 (0 to ignore).
+ */
+ uint64_t expected_category_id1;
+
+ /**
+ * Expected category id 2 (0 to ignore).
+ */
+ uint64_t expected_category_id2;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+};
+
+static bool
+product_id_matches (const json_t *product,
+ const char *expected_id)
+{
+ const json_t *id_val;
+
+ if (NULL == expected_id)
+ return false;
+ id_val = json_object_get (product,
+ "product_id");
+ if (! json_is_string (id_val))
+ return false;
+ return (0 == strcmp (json_string_value (id_val),
+ expected_id));
+}
+
+
+/**
+ * Callback for a wallet /get/templates/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param tgr HTTP response details
+ */
+static void
+wallet_get_template_cb (void *cls,
+ const struct
+ TALER_MERCHANT_WalletTemplateGetResponse *tgr)
+{
+ struct WalletGetTemplateState *wgs = cls;
+
+ wgs->igh = NULL;
+ if (wgs->http_status != tgr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ tgr->hr.http_status,
+ (int) tgr->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (wgs->is));
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ if (MHD_HTTP_OK == tgr->hr.http_status)
+ {
+ const json_t *template_contract = tgr->details.ok.template_contract;
+ const json_t *inventory_payload;
+ const json_t *products;
+
+ inventory_payload = json_object_get (template_contract,
+ "inventory_payload");
+ if (! json_is_object (inventory_payload))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Missing inventory_payload in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ products = json_object_get (inventory_payload,
+ "products");
+ if (! json_is_array (products))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Missing products in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ if ( (0 < wgs->expected_products_len) &&
+ (json_array_size (products) != wgs->expected_products_len) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected products length\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+
+ {
+ bool found_unit = (NULL == wgs->expected_product_id);
+ bool found_extra = (NULL == wgs->expected_product_id2);
+ bool found_extra2 = (NULL == wgs->expected_product_id3);
+
+ for (size_t i = 0; i < json_array_size (products); i++)
+ {
+ const json_t *product = json_array_get (products,
+ i);
+
+ if (product_id_matches (product,
+ wgs->expected_product_id))
+ {
+ const json_t *unit_val;
+ const json_t *allow_val;
+ const json_t *prec_val;
+
+ unit_val = json_object_get (product,
+ "unit");
+ allow_val = json_object_get (product,
+ "unit_allow_fraction");
+ prec_val = json_object_get (product,
+ "unit_precision_level");
+ if ( (NULL == unit_val) ||
+ (! json_is_string (unit_val)) ||
+ (0 != strcmp (json_string_value (unit_val),
+ wgs->expected_unit)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected unit in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ if ( (! json_is_boolean (allow_val)) ||
+ (json_boolean_value (allow_val) !=
+ wgs->expected_unit_allow_fraction) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected unit_allow_fraction in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ if ( (! json_is_integer (prec_val)) ||
+ ((uint32_t) json_integer_value (prec_val) !=
+ wgs->expected_unit_precision_level) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected unit_precision_level in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ found_unit = true;
+ }
+ if (product_id_matches (product,
+ wgs->expected_product_id2))
+ found_extra = true;
+ if (product_id_matches (product,
+ wgs->expected_product_id3))
+ found_extra2 = true;
+ }
+ if (! found_unit || ! found_extra || ! found_extra2)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected product ids missing in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ }
+
+ if (NULL != wgs->expected_unit_name_short_i18n)
+ {
+ const json_t *units;
+ bool found_unit_i18n = false;
+
+ units = json_object_get (inventory_payload,
+ "units");
+ if (! json_is_array (units))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Missing units in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ for (size_t i = 0; i < json_array_size (units); i++)
+ {
+ const json_t *unit = json_array_get (units,
+ i);
+ const json_t *unit_id;
+ const json_t *unit_i18n;
+
+ unit_id = json_object_get (unit,
+ "unit");
+ if (! json_is_string (unit_id))
+ continue;
+ if (0 != strcmp (json_string_value (unit_id),
+ wgs->expected_unit))
+ continue;
+ unit_i18n = json_object_get (unit,
+ "unit_name_short_i18n");
+ if ( (NULL == unit_i18n) ||
+ (! json_is_object (unit_i18n)) ||
+ (1 != json_equal (unit_i18n,
+ wgs->expected_unit_name_short_i18n)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected unit_name_short_i18n in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ found_unit_i18n = true;
+ break;
+ }
+ if (! found_unit_i18n)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected unit entry missing in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ }
+
+ if ( (0 != wgs->expected_category_id1) ||
+ (0 != wgs->expected_category_id2) )
+ {
+ const json_t *categories;
+ bool found_cat1 = (0 == wgs->expected_category_id1);
+ bool found_cat2 = (0 == wgs->expected_category_id2);
+
+ categories = json_object_get (inventory_payload,
+ "categories");
+ if (! json_is_array (categories))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Missing categories in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ for (size_t i = 0; i < json_array_size (categories); i++)
+ {
+ const json_t *category = json_array_get (categories,
+ i);
+ const json_t *cid;
+
+ cid = json_object_get (category,
+ "category_id");
+ if (! json_is_integer (cid))
+ continue;
+ if ( (0 != wgs->expected_category_id1) &&
+ ((uint64_t) json_integer_value (cid)
+ == wgs->expected_category_id1) )
+ found_cat1 = true;
+ if ( (0 != wgs->expected_category_id2) &&
+ ((uint64_t) json_integer_value (cid)
+ == wgs->expected_category_id2) )
+ found_cat2 = true;
+ }
+ if (! found_cat1 || ! found_cat2)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected category ids missing in wallet template\n");
+ TALER_TESTING_interpreter_fail (wgs->is);
+ return;
+ }
+ }
+ }
+ TALER_TESTING_interpreter_next (wgs->is);
+}
+
+
+/**
+ * Run the "GET /templates/$ID" wallet CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+wallet_get_template_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct WalletGetTemplateState *wgs = cls;
+
+ wgs->is = is;
+ wgs->igh = TALER_MERCHANT_wallet_template_get (
+ TALER_TESTING_interpreter_get_context (is),
+ wgs->merchant_url,
+ wgs->template_id,
+ &wallet_get_template_cb,
+ wgs);
+ GNUNET_assert (NULL != wgs->igh);
+}
+
+
+/**
+ * Free the state of a "GET template" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+wallet_get_template_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct WalletGetTemplateState *wgs = cls;
+
+ if (NULL != wgs->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /templates/$ID operation did not complete\n");
+ TALER_MERCHANT_wallet_template_get_cancel (wgs->igh);
+ }
+ json_decref (wgs->expected_unit_name_short_i18n);
+ GNUNET_free (wgs);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_wallet_get_template (
+ const char *label,
+ const char *merchant_url,
+ const char *template_id,
+ size_t expected_products_len,
+ const char *expected_product_id,
+ const char *expected_unit,
+ bool expected_unit_allow_fraction,
+ uint32_t expected_unit_precision_level,
+ json_t *expected_unit_name_short_i18n,
+ const char *expected_product_id2,
+ const char *expected_product_id3,
+ uint64_t expected_category_id1,
+ uint64_t expected_category_id2,
+ unsigned int http_status)
+{
+ struct WalletGetTemplateState *wgs;
+
+ wgs = GNUNET_new (struct WalletGetTemplateState);
+ wgs->merchant_url = merchant_url;
+ wgs->template_id = template_id;
+ wgs->expected_products_len = expected_products_len;
+ wgs->expected_product_id = expected_product_id;
+ wgs->expected_unit = expected_unit;
+ wgs->expected_unit_allow_fraction = expected_unit_allow_fraction;
+ wgs->expected_unit_precision_level = expected_unit_precision_level;
+ if (NULL != expected_unit_name_short_i18n)
+ wgs->expected_unit_name_short_i18n =
+ json_incref (expected_unit_name_short_i18n);
+ wgs->expected_product_id2 = expected_product_id2;
+ wgs->expected_product_id3 = expected_product_id3;
+ wgs->expected_category_id1 = expected_category_id1;
+ wgs->expected_category_id2 = expected_category_id2;
+ wgs->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = wgs,
+ .label = label,
+ .run = &wallet_get_template_run,
+ .cleanup = &wallet_get_template_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_wallet_get_template.c */