merchant

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

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:
Msrc/include/taler_merchant_util.h | 114++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/util/contract_parse.c | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/util/contract_serialize.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
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",