commit 7501c7ae60c302e4074fc0d56ead1c22f05aaf4c
parent e0cf46fd0f699db3aa72dfc7f4118b80bb0993d8
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 29 Dec 2025 04:57:41 +0100
expand contract parser to also go into the products array
Diffstat:
3 files changed, 312 insertions(+), 11 deletions(-)
diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- Copyright (C) 2024 Taler Systems SA
+ Copyright (C) 2024, 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
@@ -616,6 +616,86 @@ struct TALER_MERCHANT_ContractTokenFamily
/**
+ * Details about a product to be sold.
+ */
+struct TALER_MERCHANT_Product
+{
+
+ /**
+ * Merchant-internal identifier for the product. NULL for none.
+ */
+ char *product_id;
+
+ /**
+ * Name of the product. NULL for pre **v20** contract terms.
+ */
+ char *product_name;
+
+ /**
+ * Human-readable product description.
+ */
+ char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized descriptions.
+ * NULL if no translations are available.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Legacy integer portion of the quantity to deliver;
+ * 0 if @e unit_quantity is be used.
+ * FIXME: deprecated? If so, when?
+ */
+ uint64_t quantity;
+
+ /**
+ * Preferred quantity string using "<integer>[.<fraction>]" syntax with up to six fractional digits. NULL if @e quantity is used.
+ */
+ char *unit_quantity;
+
+ /**
+ * Unit in which the product is measured (liters, kilograms, packages,
+ * etc.). Can be NULL if not specified.
+ */
+ char *unit;
+
+ /**
+ * The price of the product; this is the total price for quantity times unit
+ * of this product.
+ * TODO: possible interpretations will likely change in the future
+ * once we add taxes, need is_net_price indicator!
+ */
+ struct TALER_Amount price;
+
+ /**
+ * An optional base64-encoded image of the product.
+ */
+ char *image;
+
+ /**
+ * A list of taxes paid by the merchant for this product. Can be NULL.
+ * Will likely change soon!
+ */
+ json_t *taxes;
+
+ /**
+ * Time indicating when this product should be delivered.
+ * #GNUNET_TIME_UNIT_FOREVER_TS for unknown / not specified.
+ */
+ struct GNUNET_TIME_Timestamp delivery_date;
+
+ /**
+ * Money pot to use for this product, overrides value from
+ * the inventory if given. Not useful to wallets, only for
+ * merchant-internal accounting.
+ */
+ uint64_t product_money_pot;
+
+};
+
+
+/**
* Struct to hold contract terms.
*/
struct TALER_MERCHANT_Contract
@@ -700,9 +780,14 @@ struct TALER_MERCHANT_Contract
json_t *fulfillment_message_i18n;
/**
+ * Length of the @e products array.
+ */
+ size_t products_len;
+
+ /**
* Array of products that are part of the purchase.
*/
- json_t *products;
+ struct TALER_MERCHANT_Product *products;
/**
* Timestamp of the contract.
@@ -743,13 +828,12 @@ struct TALER_MERCHANT_Contract
/**
* The hash of the merchant instance's wire details.
- * TODO: appropriate type
*/
struct TALER_MerchantWireHashP h_wire;
/**
* Wire transfer method identifier for the wire method associated with
- h_wire.
+ * @e h_wire.
*/
char *wire_method;
@@ -890,6 +974,19 @@ TALER_MERCHANT_parse_choice_input (
/**
+ * Parse JSON product given in @a p, returning the result in
+ * @a r.
+ *
+ * @param p JSON specifying a ``Product`` to parse
+ * @param[out] r where to write the result
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_parse_product (const json_t *p,
+ struct TALER_MERCHANT_Product *r);
+
+
+/**
* Provide specification to parse an JSON contract output type.
* The value is provided as a descriptive string.
*
@@ -989,6 +1086,15 @@ TALER_MERCHANT_contract_choice_free (
/**
+ * Release memory inside of @a product, but not @a product itself.
+ *
+ * @param[in] product data structure to clean up
+ */
+void
+TALER_MERCHANT_product_free (struct TALER_MERCHANT_Product *product);
+
+
+/**
* Free the @a contract and all fields in it.
*
* @param[in] contract contract to free
diff --git a/src/util/contract_parse.c b/src/util/contract_parse.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2024 Taler Systems SA
+ (C) 2024, 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
@@ -17,6 +17,7 @@
* @file util/contract_parse.c
* @brief shared logic for contract terms parsing
* @author Iván Ávalos
+ * @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_common.h>
@@ -1106,12 +1107,96 @@ parse_contract_v1 (
}
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_parse_product (const json_t *p,
+ struct TALER_MERCHANT_Product *r)
+{
+ bool have_quantity;
+ bool have_unit_quantity;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("product_id",
+ &r->product_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("product_name",
+ &r->product_name),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("description",
+ &r->description),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_copy ("description_i18n",
+ &r->description_i18n),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("quantity",
+ &r->quantity),
+ &have_quantity),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("unit_quantity",
+ &r->unit_quantity),
+ &have_unit_quantity),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("unit",
+ &r->unit),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("price",
+ &r->price),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("image",
+ &r->image),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_copy ("taxes",
+ &r->taxes),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("delivery_date",
+ &r->delivery_date),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("product_money_pot",
+ &r->product_money_pot),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
+
+ r->delivery_date = GNUNET_TIME_UNIT_FOREVER_TS;
+ res = GNUNET_JSON_parse (p,
+ spec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse product at field %s\n",
+ ename);
+ return GNUNET_SYSERR;
+ }
+ if (have_quantity && have_unit_quantity)
+ {
+ GNUNET_break (0);
+ r->quantity = 0;
+ }
+ return GNUNET_OK;
+}
+
+
struct TALER_MERCHANT_Contract *
TALER_MERCHANT_contract_parse (json_t *input,
bool nonce_optional)
{
struct TALER_MERCHANT_Contract *contract
= GNUNET_new (struct TALER_MERCHANT_Contract);
+ const json_t *products = NULL;
struct GNUNET_JSON_Specification espec[] = {
spec_contract_version ("version",
&contract->version),
@@ -1140,8 +1225,10 @@ TALER_MERCHANT_contract_parse (json_t *input,
GNUNET_JSON_spec_object_copy ("fulfillment_message_i18n",
&contract->fulfillment_message_i18n),
NULL),
- GNUNET_JSON_spec_array_copy ("products",
- &contract->products),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("products",
+ &products),
+ NULL),
GNUNET_JSON_spec_timestamp ("timestamp",
&contract->timestamp),
GNUNET_JSON_spec_timestamp ("refund_deadline",
@@ -1209,7 +1296,31 @@ TALER_MERCHANT_contract_parse (json_t *input,
ename);
goto cleanup;
}
+ if (NULL != products)
+ {
+ contract->products_len = json_array_size (products);
+ if (0 != contract->products_len)
+ {
+ size_t i;
+ json_t *p;
+ contract->products = GNUNET_new_array (contract->products_len,
+ struct TALER_MERCHANT_Product);
+ json_array_foreach (products, i, p)
+ {
+ if (GNUNET_OK !=
+ TALER_MERCHANT_parse_product (p,
+ &contract->products[i]))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse product at offset %u\n",
+ (unsigned int) i);
+ goto cleanup;
+ }
+ }
+ }
+ }
switch (contract->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
@@ -1238,6 +1349,20 @@ cleanup:
void
+TALER_MERCHANT_product_free (struct TALER_MERCHANT_Product *product)
+{
+ GNUNET_free (product->product_id);
+ GNUNET_free (product->product_name);
+ GNUNET_free (product->description);
+ json_decref (product->description_i18n);
+ GNUNET_free (product->unit_quantity);
+ GNUNET_free (product->unit);
+ GNUNET_free (product->image);
+ json_decref (product->taxes);
+}
+
+
+void
TALER_MERCHANT_contract_free (
struct TALER_MERCHANT_Contract *contract)
{
@@ -1270,8 +1395,9 @@ TALER_MERCHANT_contract_free (
}
if (NULL != contract->products)
{
- json_decref (contract->products);
- contract->products = NULL;
+ for (size_t i = 0; i<contract->products_len; i++)
+ TALER_MERCHANT_product_free (&contract->products[i]);
+ GNUNET_free (contract->products);
}
GNUNET_free (contract->wire_method);
if (NULL != contract->exchanges)
diff --git a/src/util/contract_serialize.c b/src/util/contract_serialize.c
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- Copyright (C) 2024 Taler Systems SA
+ Copyright (C) 2024, 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
@@ -17,6 +17,7 @@
* @file util/contract_serialize.c
* @brief shared logic for contract terms serialization
* @author Iván Ávalos
+ * @author Christian Grothoff
*/
#include "platform.h"
@@ -384,12 +385,70 @@ json_from_contract_v1 (
}
+/**
+ * Serialize @a p to JSON.
+ *
+ * @param p product to serialize
+ * @return JSON object representing the product @a p
+ */
+static json_t *
+product_serialize (
+ const struct TALER_MERCHANT_Product *p)
+{
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("product_id",
+ p->product_id)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("product_name",
+ p->product_name)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("description",
+ p->description)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("description_i18n",
+ p->description_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ (NULL != p->unit_quantity)
+ ? GNUNET_JSON_pack_string ("unit_quantity",
+ p->unit_quantity)
+ : ( (0 != p->quantity)
+ ? GNUNET_JSON_pack_uint64 ("quantity",
+ p->quantity)
+ : GNUNET_JSON_pack_string ("dummy",
+ NULL) ) ),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("unit",
+ p->unit)),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("price",
+ TALER_amount_is_valid (&p->price)
+ ? &p->price
+ : NULL)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("image",
+ p->image)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("taxes",
+ p->taxes)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_TIME_absolute_is_never (p->delivery_date.abs_time)
+ ? GNUNET_JSON_pack_string ("dummy",
+ NULL)
+ : GNUNET_JSON_pack_timestamp ("delivery_date",
+ p->delivery_date)),
+ GNUNET_JSON_pack_uint64 ("product_money_pot",
+ p->product_money_pot));
+}
+
+
json_t *
TALER_MERCHANT_contract_serialize (
const struct TALER_MERCHANT_Contract *input,
bool nonce_optional)
{
json_t *details;
+ json_t *products;
switch (input->version)
{
@@ -408,6 +467,16 @@ TALER_MERCHANT_contract_serialize (
return NULL;
success:
+ products = json_array ();
+ GNUNET_assert (NULL != products);
+ for (size_t i = 0; i<input->products_len; i++)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (products,
+ product_serialize (&input->products[i])));
+ }
+
return GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("version",
input->version),
@@ -431,7 +500,7 @@ success:
GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
input->fulfillment_message_i18n)),
GNUNET_JSON_pack_array_steal ("products",
- input->products),
+ products),
GNUNET_JSON_pack_timestamp ("timestamp",
input->timestamp),
GNUNET_JSON_pack_timestamp ("refund_deadline",