merchant

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

commit 9a395a36f47eb9569e3a157bef0e8684ebb5ee4b
parent 6c3adf08b475542975ee3be2802b2429ea50fa35
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 24 May 2026 22:46:33 +0200

more major refactoring of parsing/serialization API

Diffstat:
Msrc/include/taler/taler_merchant_util.h | 588+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Asrc/util/base_terms_parse.c | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util/base_terms_serialize.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util/contract_choice_parse.c | 468+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util/contract_choice_serialize.c | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/util/contract_parse.c | 656+++++++++++++++++++++----------------------------------------------------------
Msrc/util/contract_serialize.c | 435+++++++------------------------------------------------------------------------
Msrc/util/meson.build | 8+++++++-
Asrc/util/order_choice_parse.c | 463+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/util/order_parse.c | 539++++++++++++++-----------------------------------------------------------------
Asrc/util/product_sold_serialize.c | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/util/template_parse.c | 20++++++++++++--------
Asrc/util/token_family_serialize.c | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 2294 insertions(+), 1580 deletions(-)

diff --git a/src/include/taler/taler_merchant_util.h b/src/include/taler/taler_merchant_util.h @@ -355,7 +355,7 @@ struct TALER_MERCHANT_ContractInput /** - * Order input (part of the v1 orders and templates). + * Order input (part of the v1 orders). */ struct TALER_MERCHANT_OrderInput { @@ -527,7 +527,7 @@ struct TALER_MERCHANT_ContractOutput /** - * Order output (part of the v1 orders and templates). + * Order output (part of the v1 orders). */ struct TALER_MERCHANT_OrderOutput { @@ -570,14 +570,9 @@ struct TALER_MERCHANT_OrderOutput struct TALER_Amount amount; /** - * Base URLs of the donation authorities that will issue the tax receipt. - */ - char **donau_urls; - - /** - * Length of the @e donau_urls array. + * True if @e amount is NOT set. */ - unsigned int donau_urls_len; + bool no_amount; } donation_receipt; @@ -612,6 +607,7 @@ struct TALER_MERCHANT_OrderOutput }; + /** * Contract choice (part of the v1 contract terms). */ @@ -675,7 +671,7 @@ struct TALER_MERCHANT_ContractChoice /** - * Order choice (part of the v1 order and template). + * Order choice (part of the v1 order). */ struct TALER_MERCHANT_OrderChoice { @@ -713,6 +709,11 @@ struct TALER_MERCHANT_OrderChoice struct TALER_Amount max_fee; /** + * True if @e max_fee was not provided. + */ + bool no_max_fee; + + /** * List of inputs the wallet must provision (all of them) to satisfy the * conditions for the order. */ @@ -967,7 +968,7 @@ struct TALER_MERCHANT_TemplateContractPaivana * from by selecting the respective index when signing the deposit * confirmation. */ - struct TALER_MERCHANT_OrderChoice *choices; + struct TALER_MERCHANT_TemplateChoice *choices; /** * Length of the @e choices array. @@ -1211,7 +1212,11 @@ TALER_MERCHANT_amount_multiply_by_quantity ( /** - * Details about a product (to be) sold. + * Details about a product (to be) sold. This structure represents + * both the MinimalInventoryProduct and the ProductSold. Note that + * the two have different mandatory and/or optional fields but are + * both possible in `struct TALER_MERCHANT_Order`. However, only + * ProductSold is allowed in the contracts. */ struct TALER_MERCHANT_ProductSold { @@ -1337,25 +1342,26 @@ struct TALER_MERCHANT_MetaData /** - * Struct to hold terms common to both orders and contracts. + * Struct to hold terms common to both orders and contracts and + * are basically present (or optional) all the time. */ -struct TALER_MERCHANT_CommonTerms +struct TALER_MERCHANT_ContractBaseTerms { /** - * Merchant public key. + * Version of the contract terms. */ - struct TALER_MerchantPublicKeyP merchant_pub; + enum TALER_MERCHANT_ContractVersion version; /** - * The hash of the merchant instance's wire details. + * Summary of the contract. */ - struct TALER_MerchantWireHashP h_wire; + char *summary; /** - * URL where the same contract could be ordered again (if available). + * Internationalized summary. Optional. */ - char *public_reorder_url; + json_t *summary_i18n; /** * Our order ID. @@ -1363,158 +1369,133 @@ struct TALER_MERCHANT_CommonTerms char *order_id; /** - * Merchant base URL. - */ - char *merchant_base_url; - - /** - * Metadata about the merchant. - */ - struct TALER_MERCHANT_MetaData merchant; - - /** - * Summary of the contract. - */ - char *summary; - - /** - * Internationalized summary. + * URL where the same contract could be ordered again (if available). + * Optional. */ - json_t *summary_i18n; + char *public_reorder_url; /** * URL that will show that the contract was successful * after it has been paid for. + * Optional. */ char *fulfillment_url; /** * Message shown to the customer after paying for the contract. * Either fulfillment_url or fulfillment_message must be specified. + * Optional. */ char *fulfillment_message; /** * Map from IETF BCP 47 language tags to localized fulfillment messages. + * Optional. */ json_t *fulfillment_message_i18n; /** - * Wire transfer method identifier for the wire method associated with - * @e h_wire. - */ - char *wire_method; - - /** - * Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - * TODO: appropriate type - */ - json_t *exchanges; - - /** * Delivery location. + * Optional. */ json_t *delivery_location; /** - * Extra data that is only interpreted by the merchant frontend. - */ - json_t *extra; - - /** - * Array of products that are part of the purchase. - */ - struct TALER_MERCHANT_ProductSold *products; - - /** - * Length of the @e products array. - */ - size_t products_len; - - /** - * Timestamp of the contract. - */ - struct GNUNET_TIME_Timestamp timestamp; - - /** - * Deadline for refunds. + * Delivery date. + * Optional. */ - struct GNUNET_TIME_Timestamp refund_deadline; + struct GNUNET_TIME_Timestamp delivery_date; /** * Specifies for how long the wallet should try to get an * automatic refund for the purchase. + * Optional, zero if not applicable. */ struct GNUNET_TIME_Relative auto_refund; /** - * Payment deadline. + * Extra data that is only interpreted by the merchant frontend. */ - struct GNUNET_TIME_Timestamp pay_deadline; + json_t *extra; /** - * Wire transfer deadline. + * Minimum age the buyer must have (in years). */ - struct GNUNET_TIME_Timestamp wire_deadline; + uint8_t minimum_age; /** - * Delivery date. + * Default money pot to use for this product, applies to the + * amount remaining that was not claimed by money pots of + * products or taxes. Not useful to wallets, only for + * merchant-internal accounting. If zero, the remaining + * account is simply not accounted for in any money pot. */ - struct GNUNET_TIME_Timestamp delivery_date; + uint64_t default_money_pot; /** * Latest time until which the good or service may be * picked up by the customer. This is usually for digital * goods where the customer has a finite window for downloading. + * Optional. FOREVER if not set. */ struct GNUNET_TIME_Timestamp max_pickup_time; +}; + + +/** + * Struct to hold an order. + */ +struct TALER_MERCHANT_Order +{ + /** - * Default money pot to use for this product, applies to the - * amount remaining that was not claimed by money pots of - * products or taxes. Not useful to wallets, only for - * merchant-internal accounting. If zero, the remaining - * account is simply not accounted for in any money pot. + * Shared base terms of contracts and orders. */ - uint64_t default_money_pot; + struct TALER_MERCHANT_ContractBaseTerms *base; /** - * Specified version of the contract. + * Array of products that are part of the purchase. */ - enum TALER_MERCHANT_ContractVersion version; + struct TALER_MERCHANT_ProductSold *products; /** - * Minimum age the buyer must have (in years). + * Length of the @e products array. */ - uint8_t minimum_age; + size_t products_len; -}; + /** + * Timestamp of the contract. + * Optional. Set to "now" on parsing if not given. + */ + struct GNUNET_TIME_Timestamp timestamp; -/** - * Struct to hold contract terms. - */ -struct TALER_MERCHANT_Contract -{ + /** + * Deadline for refunds. + * Optional. Set to FOREVER if not given. + */ + struct GNUNET_TIME_Timestamp refund_deadline; /** - * Terms common with contracts and orders. + * Payment deadline. + * Optional. Set to zero if not given. */ - struct TALER_MERCHANT_CommonTerms common; + struct GNUNET_TIME_Timestamp pay_deadline; /** - * Nonce generated by the wallet and echoed by the merchant - * in this field when the proposal is generated. + * Wire transfer deadline. + * Optional. Set to FOREVER if not given. */ - char *nonce; + struct GNUNET_TIME_Timestamp wire_transfer_deadline; /** - * Details depending on the @e version. + * Details depending on the @e base.version. */ union { /** - * Details for v0 contracts. + * Details for v0 orders. */ struct { @@ -1538,10 +1519,15 @@ struct TALER_MERCHANT_Contract bool no_tip; /** - * Maximum fee as given by the client request. - */ + * Maximum fee as given by the client request. + */ struct TALER_Amount max_fee; + /** + * True if @e max_fee was not provided. + */ + bool no_max_fee; + } v0; /** @@ -1551,27 +1537,17 @@ struct TALER_MERCHANT_Contract { /** - * Array of possible specific contracts the wallet/customer may choose + * Array of possible specific payment choices the wallet/customer may choose * from by selecting the respective index when signing the deposit - * confirmation. +2 * confirmation. */ - struct TALER_MERCHANT_ContractChoice *choices; + struct TALER_MERCHANT_OrderChoice *choices; /** * Length of the @e choices array. */ unsigned int choices_len; - /** - * Array of token authorities. - */ - struct TALER_MERCHANT_ContractTokenFamily *token_authorities; - - /** - * Length of the @e token_authorities array. - */ - unsigned int token_authorities_len; - } v1; } details; @@ -1580,15 +1556,77 @@ struct TALER_MERCHANT_Contract /** - * Struct to hold order. + * Struct to hold proto-contracts. */ -struct TALER_MERCHANT_Order +struct TALER_MERCHANT_ProtoContract { /** - * Terms common with contracts and orders. + * Shared base terms of contracts and orders. + */ + struct TALER_MERCHANT_ContractBaseTerms *base; + + /** + * Timestamp of the contract. + */ + struct GNUNET_TIME_Timestamp timestamp; + + /** + * Deadline for refunds. + */ + struct GNUNET_TIME_Timestamp refund_deadline; + + /** + * Payment deadline. */ - struct TALER_MERCHANT_CommonTerms common; + struct GNUNET_TIME_Timestamp pay_deadline; + + /** + * Wire transfer deadline. + */ + struct GNUNET_TIME_Timestamp wire_deadline; + + /** + * Merchant public key. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Merchant base URL. + */ + char *merchant_base_url; + + /** + * Metadata about the merchant. + */ + struct TALER_MERCHANT_MetaData merchant; + + /** + * Array of products that are part of the purchase. + */ + struct TALER_MERCHANT_ProductSold *products; + + /** + * Length of the @e products array. + */ + size_t products_len; + + /** + * The hash of the merchant instance's wire details. + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * Wire transfer method identifier for the wire method associated with + * @e h_wire. + */ + char *wire_method; + + /** + * Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + * TODO: appropriate type + */ + json_t *exchanges; /** * Details depending on the @e version. @@ -1621,8 +1659,8 @@ struct TALER_MERCHANT_Order bool no_tip; /** - * Maximum fee as given by the client request. - */ + * Maximum fee as given by the client request. + */ struct TALER_Amount max_fee; } v0; @@ -1634,11 +1672,11 @@ struct TALER_MERCHANT_Order { /** - * Array of possible specific payment choices the wallet/customer may choose + * Array of possible specific contracts the wallet/customer may choose * from by selecting the respective index when signing the deposit * confirmation. */ - struct TALER_MERCHANT_OrderChoice *choices; + struct TALER_MERCHANT_ContractChoice *choices; /** * Length of the @e choices array. @@ -1663,60 +1701,23 @@ struct TALER_MERCHANT_Order /** - * Parse common terms (of orders and contracts) in @a input to - * initialize @a ct. - * - * @param[out] ct common terms to initialize - * @param[in] input JSON object containing contract terms - * @return true on success, false if @a input is malformed - */ -bool -TALER_MERCHANT_common_terms_parse ( - struct TALER_MERCHANT_CommonTerms *ct, - json_t *input); - - -/** - * Release memory in @a ct, but not @a ct itself. - * - * @param[in,out] ct common terms to clean up - */ -void -TALER_MERCHANT_common_terms_free ( - struct TALER_MERCHANT_CommonTerms *ct); - - -/** - * Get JSON representation of merchant details. - * - * @param[in] merchant metadata to serialize - * @return JSON object with merchant details; NULL on error + * Struct to hold contract terms. */ -json_t * -TALER_MERCHANT_metadata_to_json ( - const struct TALER_MERCHANT_MetaData *merchant); - +struct TALER_MERCHANT_Contract +{ -/** - * Parse JSON contract terms in @a input. - * - * @param[in] input JSON object containing contract terms - * @return parsed contract terms; NULL if @a input is malformed - */ -struct TALER_MERCHANT_Contract * -TALER_MERCHANT_contract_parse ( - json_t *input); + /** + * The proto-contract. + */ + struct TALER_MERCHANT_ProtoContract *pc; + /** + * Nonce generated by the wallet and echoed by the merchant + * in this field when the proposal is generated. + */ + char *nonce; -/** - * Parse JSON order in @a input. - * - * @param[in] input JSON object containing an order - * @return parsed contract terms; NULL if @a input is malformed - */ -struct TALER_MERCHANT_Order * -TALER_MERCHANT_order_parse ( - json_t *input); +}; /** @@ -1761,6 +1762,17 @@ TALER_MERCHANT_spec_merchant_details ( /** + * Get JSON representation of merchant details. + * + * @param[in] merchant metadata to serialize + * @return JSON object with merchant details; NULL on error + */ +json_t * +TALER_MERCHANT_metadata_to_json ( + const struct TALER_MERCHANT_MetaData *merchant); + + +/** * Provide specification to parse given JSON array to order * choices. All fields from @a choices elements are copied. * @@ -1813,6 +1825,7 @@ TALER_MERCHANT_spec_token_families ( * @param index index of choice input in inputs array * @return #GNUNET_SYSERR if @a input is malformed; #GNUNET_OK otherwise */ +// FIXME: make static? enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_contract_choice_input ( json_t *root, @@ -1828,6 +1841,7 @@ TALER_MERCHANT_parse_contract_choice_input ( * @param index index of choice input in inputs array * @return #GNUNET_SYSERR if @a input is malformed; #GNUNET_OK otherwise */ +// FIXME: make static? enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_order_choice_input ( json_t *root, @@ -1849,20 +1863,6 @@ TALER_MERCHANT_taxes_array_valid ( /** - * Parse JSON product given in @a p, returning the result in - * @a r. - * - * @param p JSON specifying a ``ProductSold`` to parse - * @param[out] r where to write the result - * @return #GNUNET_OK on success - */ -enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_product_sold ( - const json_t *p, - struct TALER_MERCHANT_ProductSold *r); - - -/** * Provide specification to parse an JSON contract output type. * The value is provided as a descriptive string. * @@ -1884,6 +1884,7 @@ TALER_MERCHANT_json_spec_cot ( * @param index index of choice output in outputs array * @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise */ +// FIXME: make static? enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_contract_choice_output ( json_t *root, @@ -1899,6 +1900,7 @@ TALER_MERCHANT_parse_contract_choice_output ( * @param index index of choice output in outputs array * @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise */ +// FIXME: make static? enum GNUNET_GenericReturnValue TALER_MERCHANT_parse_order_choice_output ( json_t *root, @@ -1907,60 +1909,48 @@ TALER_MERCHANT_parse_order_choice_output ( /** - * Serialize @a p to JSON. - * - * @param p product to serialize - * @return JSON object representing the product @a p - */ -json_t * -TALER_MERCHANT_product_sold_serialize ( - const struct TALER_MERCHANT_ProductSold *p); - - -/** - * Serialize contract terms into JSON object. + * Get JSON representation of contract choice. * - * @param[in] input contract terms to serialize - * @return JSON representation of @a input; NULL on error + * @param[in] choice contract choice to serialize + * @param order whether @a choice is contained in order or contract terms + * @return JSON representation of @a choice; NULL on error */ json_t * -TALER_MERCHANT_contract_serialize ( - const struct TALER_MERCHANT_Contract *input); +TALER_MERCHANT_json_from_contract_choice ( + const struct TALER_MERCHANT_ContractChoice *choice); /** - * Serialize order into JSON object. + * Free all the fields in the given @a choice, but not @a choice itself, since + * it is normally part of an array. * - * @param[in] input contract terms to serialize - * @param nonce_optional whether `nonce' field is optional - * @return JSON representation of @a input; NULL on error + * @param[in] choice contract terms choice to free */ -json_t * -TALER_MERCHANT_order_serialize ( - const struct TALER_MERCHANT_Order *input); +void +TALER_MERCHANT_contract_choice_free ( + struct TALER_MERCHANT_ContractChoice *choice); /** - * Get JSON representation of contract choice. + * Get JSON representation of an order choice. * * @param[in] choice contract choice to serialize - * @param order whether @a choice is contained in order or contract terms * @return JSON representation of @a choice; NULL on error */ json_t * -TALER_MERCHANT_json_from_contract_choice ( - const struct TALER_MERCHANT_ContractChoice *choice); +TALER_MERCHANT_json_from_order_choice ( + const struct TALER_MERCHANT_OrderChoice *choice); /** - * Get JSON representation of an order choice. + * Free all the fields in the given @a choice, but not @a choice itself, since + * it is normally part of an array. * - * @param[in] choice contract choice to serialize - * @return JSON representation of @a choice; NULL on error + * @param[in] choice contract terms choice to free */ -json_t * -TALER_MERCHANT_json_from_order_choice ( - const struct TALER_MERCHANT_OrderChoice *choice); +void +TALER_MERCHANT_order_choice_free ( + struct TALER_MERCHANT_OrderChoice *choice); /** @@ -1996,36 +1986,41 @@ TALER_MERCHANT_find_token_family_key ( /** - * Free all the fields in the given @a choice, but not @a choice itself, since + * Free all the fields in the given @a family, but not @a family itself, since * it is normally part of an array. * - * @param[in] choice contract terms choice to free + * @param[in] family contract token family to free */ void -TALER_MERCHANT_contract_choice_free ( - struct TALER_MERCHANT_ContractChoice *choice); +TALER_MERCHANT_contract_token_family_free ( + struct TALER_MERCHANT_ContractTokenFamily *family); /** - * Free all the fields in the given @a choice, but not @a choice itself, since - * it is normally part of an array. + * Parse JSON product given in @a p, returning the result in + * @a r. * - * @param[in] choice contract terms choice to free + * @param p JSON specifying a ``ProductSold`` to parse + * @param[out] r where to write the result + * @param allow_mip true to allow for MinimalInventoryProduct + * @return #GNUNET_OK on success */ -void -TALER_MERCHANT_order_choice_free ( - struct TALER_MERCHANT_OrderChoice *choice); +enum GNUNET_GenericReturnValue +TALER_MERCHANT_parse_product_sold ( + const json_t *p, + struct TALER_MERCHANT_ProductSold *r, + bool allow_mip); /** - * Free all the fields in the given @a family, but not @a family itself, since - * it is normally part of an array. + * Serialize @a p to JSON. * - * @param[in] family contract token family to free + * @param p product to serialize + * @return JSON object representing the product @a p */ -void -TALER_MERCHANT_contract_token_family_free ( - struct TALER_MERCHANT_ContractTokenFamily *family); +json_t * +TALER_MERCHANT_product_sold_serialize ( + const struct TALER_MERCHANT_ProductSold *p); /** @@ -2039,13 +2034,61 @@ TALER_MERCHANT_product_sold_free ( /** - * Free the @a contract and all fields in it. + * Parse base terms (of orders and contracts) in @a input. * - * @param[in] contract contract to free + * @param[in] input JSON object containing contract terms + * @return NULL if @a input is malformed + */ +struct TALER_MERCHANT_ContractBaseTerms * +TALER_MERCHANT_base_terms_parse ( + json_t *input); + + +/** + * Serialize contract base terms into JSON object. + * + * @param[in] ct base contract terms to serialize + * @return JSON representation of @a ct; NULL on error + */ +json_t * +TALER_MERCHANT_base_terms_serialize ( + const struct TALER_MERCHANT_ContractBaseTerms *ct); + + +/** + * Release memory in @a ct + * + * @param[in] ct common terms to clean up */ void -TALER_MERCHANT_contract_free ( - struct TALER_MERCHANT_Contract *contract); +TALER_MERCHANT_base_terms_free ( + struct TALER_MERCHANT_ContractBaseTerms *ct); + + +/** + * Parse JSON order in @a input. + * + * @param[in] input JSON object containing an order + * @return parsed contract terms; NULL if @a input is malformed + */ +struct TALER_MERCHANT_Order * +TALER_MERCHANT_order_parse ( + json_t *input); + + +#if NOT_NEEDED_YET +/** + * Serialize order into JSON object. + * + * @param[in] input contract terms to serialize + * @param nonce_optional whether `nonce' field is optional + * @return JSON representation of @a input; NULL on error + */ +json_t * +TALER_MERCHANT_order_serialize ( + const struct TALER_MERCHANT_Order *input); + +#endif /** @@ -2058,4 +2101,69 @@ TALER_MERCHANT_order_free ( struct TALER_MERCHANT_Order *order); +/** + * Parse JSON proto contract terms in @a input. + * + * @param[in] input JSON object containing contract terms + * @param[out] pc proto contract to initialize + * @return true on success, false on failure + */ +bool +TALER_MERCHANT_proto_contract_parse ( + json_t *input, + struct TALER_MERCHANT_ProtoContract *pc); + + +/** + * Serialize proto contract into JSON object. + * + * @param[in] pc proto contract to serialize + * @return JSON representation of @a pc; NULL on error + */ +json_t * +TALER_MERCHANT_proto_contract_serialize ( + const struct TALER_MERCHANT_ProtoContract *pc); + +/** + * Free the proto-contract at @a pc + * + * @param[in] pc proto-contract to free + */ +void +TALER_MERCHANT_proto_contract_free ( + struct TALER_MERCHANT_ProtoContract *pc); + + +/** + * Parse JSON contract terms in @a input. + * + * @param[in] input JSON object containing contract terms + * @return parsed contract terms; NULL if @a input is malformed + */ +struct TALER_MERCHANT_Contract * +TALER_MERCHANT_contract_parse ( + json_t *input); + + +/** + * Serialize contract terms into JSON object. + * + * @param[in] input contract terms to serialize + * @return JSON representation of @a input; NULL on error + */ +json_t * +TALER_MERCHANT_contract_serialize ( + const struct TALER_MERCHANT_Contract *input); + + +/** + * Free the @a contract and all fields in it. + * + * @param[in] contract contract to free + */ +void +TALER_MERCHANT_contract_free ( + struct TALER_MERCHANT_Contract *contract); + + #endif diff --git a/src/util/base_terms_parse.c b/src/util/base_terms_parse.c @@ -0,0 +1,180 @@ +/* + This file is part of TALER + (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 + 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 src/util/base_terms_parse.c + * @brief shared logic for order and contract terms parsing + * @author Iván Ávalos + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <stdbool.h> +#include <stdint.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> +#include "taler/taler_merchant_util.h" + + +struct TALER_MERCHANT_ContractBaseTerms * +TALER_MERCHANT_base_terms_parse ( + json_t *input) +{ + struct TALER_MERCHANT_ContractBaseTerms *ct + = GNUNET_new (struct TALER_MERCHANT_ContractBaseTerms); + const json_t *products = NULL; + struct GNUNET_JSON_Specification espec[] = { + TALER_MERCHANT_spec_contract_version ("version", + &ct->version), + GNUNET_JSON_spec_string_copy ("summary", + &ct->summary), + /* FIXME: do i18n_str validation in the future */ + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_copy ("summary_i18n", + &ct->summary_i18n), + NULL), + GNUNET_JSON_spec_string_copy ("order_id", + &ct->order_id), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string_copy ("public_reorder_url", + &ct->public_reorder_url), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string_copy ("fulfillment_url", + &ct->fulfillment_url), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string_copy ("fulfillment_message", + &ct->fulfillment_message), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_copy ("fulfillment_message_i18n", + &ct->fulfillment_message_i18n), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_copy ("delivery_location", + &ct->delivery_location), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("delivery_date", + &ct->delivery_date), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("max_pickup_time", + &ct->max_pickup_time), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_relative_time ("auto_refund", + &ct->auto_refund), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_copy ("extra", + &ct->extra), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint8 ("minimum_age", + &ct->minimum_age), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint64 ("default_money_pot", + &ct->default_money_pot), + NULL), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + const char *ename; + unsigned int eline; + + GNUNET_assert (NULL != input); + ct->max_pickup_time = GNUNET_TIME_UNIT_FOREVER_TS; + res = GNUNET_JSON_parse (input, + espec, + &ename, + &eline); + if (GNUNET_OK != res) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse common terms at field %s\n", + ename); + goto cleanup; + } + + if (GNUNET_OK != res) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse contract\n"); + goto cleanup; + } + return true; + +cleanup: + TALER_MERCHANT_base_terms_free (ct); + return false; +} + + +void +TALER_MERCHANT_base_terms_free ( + struct TALER_MERCHANT_ContractBaseTerms *ct) +{ + if (NULL == ct) + return; + GNUNET_free (ct->public_reorder_url); + GNUNET_free (ct->order_id); + GNUNET_free (ct->merchant_base_url); + GNUNET_free (ct->merchant.name); + GNUNET_free (ct->merchant.website); + GNUNET_free (ct->merchant.email); + GNUNET_free (ct->merchant.logo); + if (NULL != ct->merchant.address) + { + json_decref (ct->merchant.address); + ct->merchant.address = NULL; + } + if (NULL != ct->merchant.jurisdiction) + { + json_decref (ct->merchant.jurisdiction); + ct->merchant.jurisdiction = NULL; + } + GNUNET_free (ct->summary); + GNUNET_free (ct->fulfillment_url); + GNUNET_free (ct->fulfillment_message); + if (NULL != ct->fulfillment_message_i18n) + { + json_decref (ct->fulfillment_message_i18n); + ct->fulfillment_message_i18n = NULL; + } + GNUNET_free (ct->wire_method); + if (NULL != ct->exchanges) + { + json_decref (ct->exchanges); + ct->exchanges = NULL; + } + if (NULL != ct->delivery_location) + { + json_decref (ct->delivery_location); + ct->delivery_location = NULL; + } + if (NULL != ct->extra) + { + json_decref (ct->extra); + ct->extra = NULL; + } + GNUNET_free (ct); +} diff --git a/src/util/base_terms_serialize.c b/src/util/base_terms_serialize.c @@ -0,0 +1,90 @@ +/* + This file is part of GNU Taler + 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 + 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 src/util/base_terms_serialize.c + * @brief shared logic for contract base terms serialization + * @author Iván Ávalos + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_common.h> +#include <taler/taler_json_lib.h> +#include <jansson.h> +#include "taler/taler_util.h" +#include "taler/taler_merchant_util.h" + + +/** + * Serialize contract base terms into JSON object. + * + * @param[in] ct base contract terms to serialize + * @return JSON representation of @a ct; NULL on error + */ +json_t * +TALER_MERCHANT_base_terms_serialize ( + const struct TALER_MERCHANT_ContractBaseTerms *ct) +{ + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("version", + ct->version), + GNUNET_JSON_pack_string ("summary", + ct->summary), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ("summary_i18n", + ct->summary_i18n)), + GNUNET_JSON_pack_string ("order_id", + ct->order_id), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("public_reorder_url", + ct->public_reorder_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_url", + ct->fulfillment_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_message", + ct->fulfillment_message)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n", + ct->fulfillment_message_i18n)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ("delivery_location", + ct->delivery_location)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("delivery_date", + ct->delivery_date)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_time_rel ("auto_refund", + ct->auto_refund)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ("extra", + ct->extra)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_uint64 ("minimum_age", + ct->minimum_age)), + (0 == ct->default_money_pot) + ? GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("dummy", + NULL)) + : GNUNET_JSON_pack_uint64 ("default_money_pot", + ct->default_money_pot), + GNUNET_TIME_absolute_is_never (ct->max_pickup_time.abs_time) + ? GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("dummy", + NULL)) + : GNUNET_JSON_pack_timestamp ("max_pickup_time", + ct->max_pickup_time)); +} diff --git a/src/util/contract_choice_parse.c b/src/util/contract_choice_parse.c @@ -0,0 +1,468 @@ +/* + This file is part of TALER + (C) 2024, 2025, 2026 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 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 src/util/contract_choice_parse.c + * @brief shared logic for contract choice parsing + * @author Iván Ávalos + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <stdbool.h> +#include <stdint.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> +#include "taler/taler_merchant_util.h" + + +enum GNUNET_GenericReturnValue +TALER_MERCHANT_parse_contract_choice_input ( + json_t *root, + struct TALER_MERCHANT_ContractInput *input, + size_t index) +{ + const char *ename; + unsigned int eline; + struct GNUNET_JSON_Specification ispec[] = { + TALER_MERCHANT_json_spec_cit ("type", + &input->type), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + ispec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + switch (input->type) + { + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: + GNUNET_break (0); + break; + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("token_family_slug", + &input->details.token.token_family_slug), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("count", + &input->details.token.count), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'type' invalid in input #%u\n", + (unsigned int) index); + GNUNET_break_op (0); + return GNUNET_SYSERR; +} + + +enum GNUNET_GenericReturnValue +TALER_MERCHANT_parse_contract_choice_output ( + json_t *root, + struct TALER_MERCHANT_ContractOutput *output, + size_t index) +{ + const char *ename; + unsigned int eline; + struct GNUNET_JSON_Specification ispec[] = { + TALER_MERCHANT_json_spec_cot ("type", + &output->type), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + ispec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + switch (output->type) + { + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_break (0); + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("token_family_slug", + &output->details.token.token_family_slug), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint ("count", + &output->details.token.count), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("valid_at", + &output->details.token.valid_at), + NULL), + GNUNET_JSON_spec_uint ("key_index", + &output->details.token.key_index), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; + } + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + { + const json_t *donau_urls = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ( + "amount", + &output->details.donation_receipt.amount), + NULL), + GNUNET_JSON_spec_array_const ("donau_urls", + &donau_urls), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + GNUNET_array_grow (output->details.donation_receipt.donau_urls, + output->details.donation_receipt.donau_urls_len, + json_array_size (donau_urls)); + + for (unsigned int i = 0; + i < output->details.donation_receipt.donau_urls_len; + i++) + { + const json_t *jurl; + + jurl = json_array_get (donau_urls, + i); + if (! json_is_string (jurl)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + output->details.donation_receipt.donau_urls[i] = + GNUNET_strdup (json_string_value (jurl)); + } + + return GNUNET_OK; + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'type' invalid in output #%u\n", + (unsigned int) index); + GNUNET_break_op (0); + return GNUNET_SYSERR; +} + + +/** + * Parse given JSON object to choices array. + * + * @param cls closure, pointer to array length + * @param root the json array representing the choices + * @param[out] ospec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_contract_choices ( + void *cls, + json_t *root, + struct GNUNET_JSON_Specification *ospec) +{ + struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr; + unsigned int *choices_len = cls; + + if (! json_is_array (root)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 == json_array_size (root)) + { + /* empty list of choices is not allowed */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *choices = NULL; + *choices_len = 0; + GNUNET_array_grow (*choices, + *choices_len, + json_array_size (root)); + + for (unsigned int i = 0; i < *choices_len; i++) + { + struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i]; + const json_t *jinputs = NULL; + const json_t *joutputs = NULL; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("amount", + &choice->amount), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("tip", + &choice->tip), + &choice->no_tip), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string_copy ("description", + &choice->description), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_copy ("description_i18n", + &choice->description_i18n), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("max_fee", + &choice->max_fee), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("inputs", + &jinputs), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("outputs", + &joutputs), + NULL), + GNUNET_JSON_spec_end () + }; + const char *ename; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (root, i), + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (! choice->no_tip) && + (GNUNET_OK != + TALER_amount_cmp_currency (&choice->amount, + &choice->tip)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Tip currency does not match amount currency in choice #%u\n", + i); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (NULL != jinputs) + { + const json_t *jinput; + size_t idx; + + json_array_foreach ((json_t *) jinputs, idx, jinput) + { + struct TALER_MERCHANT_ContractInput input = { + .details.token.count = 1 + }; + + if (GNUNET_OK != + TALER_MERCHANT_parse_contract_choice_input ((json_t *) jinput, + &input, + idx)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + switch (input.type) + { + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: + GNUNET_break_op (0); + return GNUNET_SYSERR; + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: + /* Ignore inputs tokens with 'count' field set to 0 */ + if (0 == input.details.token.count) + continue; + break; + } + GNUNET_array_append (choice->inputs, + choice->inputs_len, + input); + } + } + + if (NULL != joutputs) + { + const json_t *joutput; + size_t idx; + json_array_foreach ((json_t *) joutputs, idx, joutput) + { + struct TALER_MERCHANT_ContractOutput output = { + .details.token.count = 1 + }; + + if (GNUNET_OK != + TALER_MERCHANT_parse_contract_choice_output ((json_t *) joutput, + &output, + idx)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + switch (output.type) + { + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_break_op (0); + return GNUNET_SYSERR; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: + /* Ignore output tokens with 'count' field set to 0 */ + if (0 == output.details.token.count) + continue; + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + break; + } + GNUNET_array_append (choice->outputs, + choice->outputs_len, + output); + } + } + } + + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_MERCHANT_spec_contract_choices ( + const char *name, + struct TALER_MERCHANT_ContractChoice **choices, + unsigned int *choices_len) +{ + struct GNUNET_JSON_Specification ret = { + .cls = (void *) choices_len, + .parser = &parse_contract_choices, + .field = name, + .ptr = choices, + }; + + return ret; +} + + +void +TALER_MERCHANT_contract_choice_free ( + struct TALER_MERCHANT_ContractChoice *choice) +{ + for (unsigned int i = 0; i < choice->outputs_len; i++) + { + struct TALER_MERCHANT_ContractOutput *output = &choice->outputs[i]; + + switch (output->type) + { + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_break (0); + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + for (unsigned int j = 0; + j<output->details.donation_receipt.donau_urls_len; + j++) + GNUNET_free (output->details.donation_receipt.donau_urls[j]); + GNUNET_array_grow (output->details.donation_receipt.donau_urls, + output->details.donation_receipt.donau_urls_len, + 0); + break; +#if FUTURE + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN: + GNUNET_free (output->details.coin.exchange_url); + break; +#endif + } + } + GNUNET_free (choice->description); + if (NULL != choice->description_i18n) + { + json_decref (choice->description_i18n); + choice->description_i18n = NULL; + } + GNUNET_free (choice->inputs); + GNUNET_free (choice->outputs); +} diff --git a/src/util/contract_choice_serialize.c b/src/util/contract_choice_serialize.c @@ -0,0 +1,167 @@ +/* + This file is part of GNU Taler + 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 + 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 src/util/contract_choice_serialize.c + * @brief shared logic for contract choice serialization + * @author Iván Ávalos + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_common.h> +#include <taler/taler_json_lib.h> +#include <jansson.h> +#include "taler/taler_util.h" +#include "taler/taler_merchant_util.h" + +/** + * Get JSON representation of contract choice input. + * + * @param[in] input contract terms choice input + * @return JSON representation of @a input; NULL on error + */ +static json_t * +json_from_contract_input ( + const struct TALER_MERCHANT_ContractInput *input) +{ + switch (input->type) + { + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "invalid contract input type"); + GNUNET_assert (0); + return NULL; + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "token"), + GNUNET_JSON_pack_string ("token_family_slug", + input->details.token.token_family_slug), + GNUNET_JSON_pack_int64 ("count", + input->details.token.count)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "unsupported contract input type %d", + input->type); + GNUNET_assert (0); + return NULL; +} + + +/** + * Get JSON representation of contract choice output. + * + * @param[in] output contract terms choice output + * @return JSON representation of @a output; NULL on error + */ +static json_t * +json_from_contract_output ( + const struct TALER_MERCHANT_ContractOutput *output) +{ + switch (output->type) + { + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "invalid contract output type"); + GNUNET_assert (0); + return NULL; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "token"), + GNUNET_JSON_pack_string ("token_family_slug", + output->details.token.token_family_slug), + GNUNET_JSON_pack_uint64 ("count", + output->details.token.count), + GNUNET_JSON_pack_uint64 ("key_index", + output->details.token.key_index)); + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + { + json_t *donau_urls; + + donau_urls = json_array (); + GNUNET_assert (NULL != donau_urls); + for (unsigned i = 0; + i < output->details.donation_receipt.donau_urls_len; + i++) + GNUNET_assert (0 == + json_array_append_new ( + donau_urls, + json_string ( + output->details.donation_receipt.donau_urls[i]))); + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "tax-receipt"), + GNUNET_JSON_pack_array_steal ("donau_urls", + donau_urls), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("amount", + &output->details.donation_receipt.amount))); + } + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unsupported contract output type %d", + output->type); + GNUNET_assert (0); + return NULL; +} + + +json_t * +TALER_MERCHANT_json_from_contract_choice ( + const struct TALER_MERCHANT_ContractChoice *choice) +{ + json_t *inputs; + json_t *outputs; + + inputs = json_array (); + GNUNET_assert (NULL != inputs); + for (unsigned int i = 0; i < choice->inputs_len; i++) + GNUNET_assert (0 == + json_array_append_new (inputs, + json_from_contract_input ( + &choice->inputs[i]))); + outputs = json_array (); + GNUNET_assert (NULL != outputs); + for (unsigned int i = 0; i < choice->outputs_len; i++) + GNUNET_assert (0 == + json_array_append_new (outputs, + json_from_contract_output ( + &choice->outputs[i]))); + + return GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + &choice->amount), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("tip", + choice->no_tip + ? NULL + : &choice->tip)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("description", + choice->description)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + choice->description_i18n)), + TALER_JSON_pack_amount ("max_fee", + &choice->max_fee), + GNUNET_JSON_pack_array_steal ("inputs", + inputs), + GNUNET_JSON_pack_array_steal ("outputs", + outputs)); +} 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, 2025 Taler Systems SA + (C) 2024, 2025, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -30,443 +30,6 @@ #include "taler/taler_merchant_util.h" -enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_contract_choice_input ( - json_t *root, - struct TALER_MERCHANT_ContractInput *input, - size_t index) -{ - const char *ename; - unsigned int eline; - struct GNUNET_JSON_Specification ispec[] = { - TALER_MERCHANT_json_spec_cit ("type", - &input->type), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - ispec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - ispec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - switch (input->type) - { - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: - GNUNET_break (0); - break; - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("token_family_slug", - &input->details.token.token_family_slug), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("count", - &input->details.token.count), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - return GNUNET_OK; - } - } - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'type' invalid in input #%u\n", - (unsigned int) index); - GNUNET_break_op (0); - return GNUNET_SYSERR; -} - - -enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_contract_choice_output ( - json_t *root, - struct TALER_MERCHANT_ContractOutput *output, - size_t index) -{ - const char *ename; - unsigned int eline; - struct GNUNET_JSON_Specification ispec[] = { - TALER_MERCHANT_json_spec_cot ("type", - &output->type), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - ispec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - ispec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - switch (output->type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_break (0); - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("token_family_slug", - &output->details.token.token_family_slug), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint ("count", - &output->details.token.count), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("valid_at", - &output->details.token.valid_at), - NULL), - GNUNET_JSON_spec_uint ("key_index", - &output->details.token.key_index), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - return GNUNET_OK; - } - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - { - const json_t *donau_urls = NULL; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &output->details.donation_receipt.amount), - NULL), - GNUNET_JSON_spec_array_const ("donau_urls", - &donau_urls), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - GNUNET_array_grow (output->details.donation_receipt.donau_urls, - output->details.donation_receipt.donau_urls_len, - json_array_size (donau_urls)); - - for (unsigned int i = 0; - i < output->details.donation_receipt.donau_urls_len; - i++) - { - const json_t *jurl; - - jurl = json_array_get (donau_urls, - i); - if (! json_is_string (jurl)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - output->details.donation_receipt.donau_urls[i] = - GNUNET_strdup (json_string_value (jurl)); - } - - return GNUNET_OK; - } - } - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'type' invalid in output #%u\n", - (unsigned int) index); - GNUNET_break_op (0); - return GNUNET_SYSERR; -} - - -/** - * Parse given JSON object to choices array. - * - * @param cls closure, pointer to array length - * @param root the json array representing the choices - * @param[out] ospec where to write the data - * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error - */ -static enum GNUNET_GenericReturnValue -parse_contract_choices ( - void *cls, - json_t *root, - struct GNUNET_JSON_Specification *ospec) -{ - struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr; - unsigned int *choices_len = cls; - - if (! json_is_array (root)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 == json_array_size (root)) - { - /* empty list of choices is not allowed */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - *choices = NULL; - *choices_len = 0; - GNUNET_array_grow (*choices, - *choices_len, - json_array_size (root)); - - for (unsigned int i = 0; i < *choices_len; i++) - { - struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i]; - const json_t *jinputs = NULL; - const json_t *joutputs = NULL; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("amount", - &choice->amount), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("tip", - &choice->tip), - &choice->no_tip), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string_copy ("description", - &choice->description), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_copy ("description_i18n", - &choice->description_i18n), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("max_fee", - &choice->max_fee), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("inputs", - &jinputs), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("outputs", - &joutputs), - NULL), - GNUNET_JSON_spec_end () - }; - const char *ename; - unsigned int eline; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (root, i), - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if ( (! choice->no_tip) && - (GNUNET_OK != - TALER_amount_cmp_currency (&choice->amount, - &choice->tip)) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Tip currency does not match amount currency in choice #%u\n", - i); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (NULL != jinputs) - { - const json_t *jinput; - size_t idx; - - json_array_foreach ((json_t *) jinputs, idx, jinput) - { - struct TALER_MERCHANT_ContractInput input = { - .details.token.count = 1 - }; - - if (GNUNET_OK != - TALER_MERCHANT_parse_contract_choice_input ((json_t *) jinput, - &input, - idx)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - switch (input.type) - { - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: - GNUNET_break_op (0); - return GNUNET_SYSERR; - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: - /* Ignore inputs tokens with 'count' field set to 0 */ - if (0 == input.details.token.count) - continue; - break; - } - GNUNET_array_append (choice->inputs, - choice->inputs_len, - input); - } - } - - if (NULL != joutputs) - { - const json_t *joutput; - size_t idx; - json_array_foreach ((json_t *) joutputs, idx, joutput) - { - struct TALER_MERCHANT_ContractOutput output = { - .details.token.count = 1 - }; - - if (GNUNET_OK != - TALER_MERCHANT_parse_contract_choice_output ((json_t *) joutput, - &output, - idx)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - switch (output.type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_break_op (0); - return GNUNET_SYSERR; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - /* Ignore output tokens with 'count' field set to 0 */ - if (0 == output.details.token.count) - continue; - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - break; - } - GNUNET_array_append (choice->outputs, - choice->outputs_len, - output); - } - } - } - - return GNUNET_OK; -} - - -struct GNUNET_JSON_Specification -TALER_MERCHANT_spec_contract_choices ( - const char *name, - struct TALER_MERCHANT_ContractChoice **choices, - unsigned int *choices_len) -{ - struct GNUNET_JSON_Specification ret = { - .cls = (void *) choices_len, - .parser = &parse_contract_choices, - .field = name, - .ptr = choices, - }; - - return ret; -} - - -void -TALER_MERCHANT_contract_choice_free ( - struct TALER_MERCHANT_ContractChoice *choice) -{ - for (unsigned int i = 0; i < choice->outputs_len; i++) - { - struct TALER_MERCHANT_ContractOutput *output = &choice->outputs[i]; - - switch (output->type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_break (0); - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - for (unsigned int j = 0; - j<output->details.donation_receipt.donau_urls_len; - j++) - GNUNET_free (output->details.donation_receipt.donau_urls[j]); - GNUNET_array_grow (output->details.donation_receipt.donau_urls, - output->details.donation_receipt.donau_urls_len, - 0); - break; -#if FUTURE - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN: - GNUNET_free (output->details.coin.exchange_url); - break; -#endif - } - } - GNUNET_free (choice->description); - if (NULL != choice->description_i18n) - { - json_decref (choice->description_i18n); - choice->description_i18n = NULL; - } - GNUNET_free (choice->inputs); - GNUNET_free (choice->outputs); -} - - /** * Parse v0-specific fields of @a input JSON into @a contract. * @@ -532,26 +95,26 @@ parse_contract_v0 ( /** - * Parse v1-specific fields of @a input JSON into @a contract. + * Parse v1-specific fields of @a input JSON into @a pc. * * @param[in] input the JSON contract terms - * @param[out] contract where to write the data + * @param[out] pc where to write the data * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error */ static enum GNUNET_GenericReturnValue parse_contract_v1 ( json_t *input, - struct TALER_MERCHANT_Contract *contract) + struct TALER_MERCHANT_ProtoContract *pc) { struct GNUNET_JSON_Specification espec[] = { TALER_MERCHANT_spec_contract_choices ( "choices", - &contract->details.v1.choices, - &contract->details.v1.choices_len), + &pc->details.v1.choices, + &pc->details.v1.choices_len), TALER_MERCHANT_spec_token_families ( "token_families", - &contract->details.v1.token_authorities, - &contract->details.v1.token_authorities_len), + &pc->details.v1.token_authorities, + &pc->details.v1.token_authorities_len), GNUNET_JSON_spec_end () }; @@ -576,6 +139,161 @@ parse_contract_v1 ( } +struct TALER_MERCHANT_ProtoContract * +TALER_MERCHANT_proto_contract_parse ( + json_t *input) +{ + struct TALER_MERCHANT_ContractBaseTerms *base; + struct TALER_MERCHANT_ProtoContract *pc; + enum GNUNET_GenericReturnValue res; + + base = TALER_MERCHANT_base_terms_parse (input); + if (NULL == base) + { + GNUNET_break_op (0); + return NULL; + } + pc = GNUNET_new (struct TALER_MERCHANT_ProtoContract); + pc->base = base; + { + json_t *products; + struct GNUNET_JSON_Specification espec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("products", + &products), + NULL), + GNUNET_JSON_spec_timestamp ("timestamp", + &pc->timestamp), + GNUNET_JSON_spec_timestamp ("refund_deadline", + &pc->refund_deadline), + GNUNET_JSON_spec_timestamp ("pay_deadline", + &pc->pay_deadline), + GNUNET_JSON_spec_timestamp ("wire_transfer_deadline", + &pc->wire_deadline), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &pc->merchant_pub), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &pc->h_wire), + GNUNET_JSON_spec_string_copy ("merchant_base_url", + &pc->merchant_base_url), + TALER_MERCHANT_spec_merchant_details ("merchant", + &pc->merchant), + GNUNET_JSON_spec_string_copy ("wire_method", + &pc->wire_method), + GNUNET_JSON_spec_array_copy ("exchanges", + &pc->exchanges), + + GNUNET_JSON_spec_end () + }; + const char *ename; + unsigned int eline; + + res = GNUNET_JSON_parse (input, + espec, + &ename, + &eline); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse proto contract at field %s\n", + ename); + goto cleanup; + } + if (NULL != products) + { + pc->products_len = json_array_size (products); + if (0 != pc->products_len) + { + size_t i; + json_t *p; + + pc->products = GNUNET_new_array ( + pc->products_len, + struct TALER_MERCHANT_ProductSold); + json_array_foreach (products, i, p) + { + if (GNUNET_OK != + TALER_MERCHANT_parse_product_sold (p, + &pc->products[i], + false)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse product at offset %u\n", + (unsigned int) i); + goto cleanup; + } + } + } + } + } + switch (base->version) + { + case TALER_MERCHANT_CONTRACT_VERSION_0: + if (GNUNET_OK == + parse_contract_v0 (input, + pc)) + return pc; + GNUNET_break_op (0); + break; + case TALER_MERCHANT_CONTRACT_VERSION_1: + if (GNUNET_OK == + parse_contract_v1 (input, + pc)) + return pc; + GNUNET_break_op (0); + break; + } +cleanup: + TALER_MERCHANT_proto_contract_free (pc); + return NULL; +} + + +/** + * Free the proto-contract at @a pc + * + * @param[in] pc proto-contract to free + */ +void +TALER_MERCHANT_proto_contract_free ( + struct TALER_MERCHANT_ProtoContract *pc) +{ + switch (pc->common.version) + { + case TALER_MERCHANT_PC_VERSION_0: + break; + case TALER_MERCHANT_PC_VERSION_1: + for (unsigned int i = 0; + i < pc->details.v1.choices_len; + i++) + TALER_MERCHANT_contract_choice_free ( + &pc->details.v1.choices[i]); + GNUNET_free (pc->details.v1.choices); + for (unsigned int i = 0; + i < pc->details.v1.token_authorities_len; + i++) + TALER_MERCHANT_contract_token_family_free ( + &pc->details.v1.token_authorities[i]); + GNUNET_free (pc->details.v1.token_authorities); + break; + } + if (NULL != ct->products) + { + for (size_t i = 0; i<ct->products_len; i++) + TALER_MERCHANT_product_sold_free (&ct->products[i]); + GNUNET_free (ct->products); + } + if (NULL != pc->base) + { + TALER_MERCHANT_base_terms_free (pc->base); + pc->base = NULL; + } + GNUNET_free (pc); +} + + struct TALER_MERCHANT_Contract * TALER_MERCHANT_contract_parse (json_t *input) { @@ -591,10 +309,10 @@ TALER_MERCHANT_contract_parse (json_t *input) unsigned int eline; GNUNET_assert (NULL != input); - if (! TALER_MERCHANT_common_terms_parse (&contract->common, - input)) + contract->pc = TALER_MERCHANT_proto_contract_parse (input); + if (NULL == contract->pc) { - GNUNET_break (0); + GNUNET_break_op (0); GNUNET_free (contract); return NULL; } @@ -604,31 +322,12 @@ TALER_MERCHANT_contract_parse (json_t *input) &eline); if (GNUNET_OK != res) { - GNUNET_break (0); + GNUNET_break_op (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse contract at field %s\n", ename); goto cleanup; } - switch (contract->common.version) - { - case TALER_MERCHANT_CONTRACT_VERSION_0: - res = parse_contract_v0 (input, - contract); - break; - case TALER_MERCHANT_CONTRACT_VERSION_1: - res = parse_contract_v1 (input, - contract); - break; - } - - if (GNUNET_OK != res) - { - GNUNET_break (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse contract\n"); - goto cleanup; - } return contract; cleanup: @@ -643,24 +342,11 @@ TALER_MERCHANT_contract_free ( { if (NULL == contract) return; - GNUNET_free (contract->nonce); - switch (contract->common.version) + if (NULL != contract->pc) { - case TALER_MERCHANT_CONTRACT_VERSION_0: - break; - case TALER_MERCHANT_CONTRACT_VERSION_1: - for (unsigned int i = 0; - i < contract->details.v1.choices_len; - i++) - TALER_MERCHANT_contract_choice_free (&contract->details.v1.choices[i]); - GNUNET_free (contract->details.v1.choices); - for (unsigned int i = 0; - i < contract->details.v1.token_authorities_len; - i++) - TALER_MERCHANT_contract_token_family_free (&contract->details.v1.token_authorities[i]); - GNUNET_free (contract->details.v1.token_authorities); - break; + TALER_MERCHANT_proto_contract_free (contract->pc); + contract->pc = NULL; } - TALER_MERCHANT_common_terms_free (&contract->common); + GNUNET_free (contract->nonce); GNUNET_free (contract); } diff --git a/src/util/contract_serialize.c b/src/util/contract_serialize.c @@ -29,262 +29,6 @@ /** - * Get JSON representation of contract choice input. - * - * @param[in] input contract terms choice input - * @return JSON representation of @a input; NULL on error - */ -static json_t * -json_from_contract_input ( - const struct TALER_MERCHANT_ContractInput *input) -{ - switch (input->type) - { - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "invalid contract input type"); - GNUNET_assert (0); - return NULL; - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "token"), - GNUNET_JSON_pack_string ("token_family_slug", - input->details.token.token_family_slug), - GNUNET_JSON_pack_int64 ("count", - input->details.token.count)); - } - - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "unsupported contract input type %d", - input->type); - GNUNET_assert (0); - return NULL; -} - - -/** - * Get JSON representation of contract choice output. - * - * @param[in] output contract terms choice output - * @return JSON representation of @a output; NULL on error - */ -static json_t * -json_from_contract_output ( - const struct TALER_MERCHANT_ContractOutput *output) -{ - switch (output->type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "invalid contract output type"); - GNUNET_assert (0); - return NULL; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "token"), - GNUNET_JSON_pack_string ("token_family_slug", - output->details.token.token_family_slug), - GNUNET_JSON_pack_uint64 ("count", - output->details.token.count), - GNUNET_JSON_pack_uint64 ("key_index", - output->details.token.key_index)); - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - { - json_t *donau_urls; - - donau_urls = json_array (); - GNUNET_assert (NULL != donau_urls); - for (unsigned i = 0; - i < output->details.donation_receipt.donau_urls_len; - i++) - GNUNET_assert (0 == - json_array_append_new ( - donau_urls, - json_string ( - output->details.donation_receipt.donau_urls[i]))); - - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("type", - "tax-receipt"), - GNUNET_JSON_pack_array_steal ("donau_urls", - donau_urls), - GNUNET_JSON_pack_allow_null ( - TALER_JSON_pack_amount ("amount", - &output->details.donation_receipt.amount))); - } - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unsupported contract output type %d", - output->type); - GNUNET_assert (0); - return NULL; -} - - -json_t * -TALER_MERCHANT_json_from_contract_choice ( - const struct TALER_MERCHANT_ContractChoice *choice) -{ - json_t *inputs; - json_t *outputs; - - inputs = json_array (); - GNUNET_assert (NULL != inputs); - for (unsigned int i = 0; i < choice->inputs_len; i++) - GNUNET_assert (0 == - json_array_append_new (inputs, - json_from_contract_input ( - &choice->inputs[i]))); - outputs = json_array (); - GNUNET_assert (NULL != outputs); - for (unsigned int i = 0; i < choice->outputs_len; i++) - GNUNET_assert (0 == - json_array_append_new (outputs, - json_from_contract_output ( - &choice->outputs[i]))); - - return GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &choice->amount), - GNUNET_JSON_pack_allow_null ( - TALER_JSON_pack_amount ("tip", - choice->no_tip - ? NULL - : &choice->tip)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("description", - choice->description)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("description_i18n", - choice->description_i18n)), - TALER_JSON_pack_amount ("max_fee", - &choice->max_fee), - GNUNET_JSON_pack_array_steal ("inputs", - inputs), - GNUNET_JSON_pack_array_steal ("outputs", - outputs)); -} - - -/** - * Get JSON representation of contract token family key. - * - * @param[in] key contract token family key - * @return JSON representation of @a key; NULL on error - */ -static json_t * -json_from_token_family_key ( - const struct TALER_MERCHANT_ContractTokenFamilyKey *key) -{ - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_timestamp ("signature_validity_start", - key->valid_after), - GNUNET_JSON_pack_timestamp ("signature_validity_end", - key->valid_before), - TALER_JSON_pack_token_pub (NULL, - &key->pub)); -} - - -/** - * Get JSON representation of contract token family details. - * - * @param[in] family contract token family - * @return JSON representation of @a family->details; NULL on error - */ -static json_t * -json_from_token_family_details ( - const struct TALER_MERCHANT_ContractTokenFamily *family) -{ - switch (family->kind) - { - case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID: - break; - case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION: - { - json_t *trusted_domains; - - trusted_domains = json_array (); - GNUNET_assert (NULL != trusted_domains); - for (unsigned int i = 0; - i < family->details.subscription.trusted_domains_len; - i++) - GNUNET_assert (0 == - json_array_append_new ( - trusted_domains, - json_string ( - family->details.subscription.trusted_domains[i]))); - - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("class", - "subscription"), - GNUNET_JSON_pack_array_steal ("trusted_domains", - trusted_domains)); - } - case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT: - { - json_t *expected_domains; - - expected_domains = json_array (); - GNUNET_assert (NULL != expected_domains); - for (unsigned int i = 0; - i < family->details.discount.expected_domains_len; - i++) - GNUNET_assert (0 == - json_array_append_new ( - expected_domains, - json_string ( - family->details.discount.expected_domains[i]))); - - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("class", - "discount"), - GNUNET_JSON_pack_array_steal ("expected_domains", - expected_domains)); - } - } - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "unsupported token family kind %d", - family->kind); - GNUNET_assert (0); - return NULL; -} - - -json_t * -TALER_MERCHANT_json_from_token_family ( - const struct TALER_MERCHANT_ContractTokenFamily *family) -{ - json_t *keys; - - keys = json_array (); - GNUNET_assert (NULL != keys); - for (unsigned int i = 0; i < family->keys_len; i++) - GNUNET_assert (0 == json_array_append_new ( - keys, - json_from_token_family_key ( - &family->keys[i]))); - - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("name", - family->name), - GNUNET_JSON_pack_string ("description", - family->description), - GNUNET_JSON_pack_object_incref ("description_i18n", - family->description_i18n), - GNUNET_JSON_pack_array_steal ("keys", - keys), - GNUNET_JSON_pack_object_steal ("details", - json_from_token_family_details (family)), - GNUNET_JSON_pack_bool ("critical", - family->critical)); -} - - -/** * Get JSON object with contract terms v0-specific fields. * * @param[in] input contract terms v0 @@ -292,7 +36,7 @@ TALER_MERCHANT_json_from_token_family ( */ static json_t * json_from_contract_v0 ( - const struct TALER_MERCHANT_Contract *input) + const struct TALER_MERCHANT_ProtoContract *input) { return GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", @@ -315,7 +59,7 @@ json_from_contract_v0 ( */ static json_t * json_from_contract_v1 ( - const struct TALER_MERCHANT_Contract *input) + const struct TALER_MERCHANT_ProtoContract *input) { json_t *choices; json_t *families; @@ -346,108 +90,27 @@ json_from_contract_v1 ( /** - * Convert quantity @a q into a string for JSON serialization + * Serialize proto contract into JSON object. * - * @param q quantity to convert - * @return formatted string + * @param[in] pc proto contract to serialize + * @return JSON representation of @a pc; NULL on error */ -static const char * -quantity_to_string (const struct TALER_MERCHANT_ProductQuantity *q) -{ - static char res[64]; - - TALER_MERCHANT_vk_format_fractional_string (TALER_MERCHANT_VK_QUANTITY, - q->integer, - q->fractional, - sizeof (res), - res); - return res; -} - - json_t * -TALER_MERCHANT_product_sold_serialize ( - const struct TALER_MERCHANT_ProductSold *p) +TALER_MERCHANT_proto_contract_serialize ( + const struct TALER_MERCHANT_ProtoContract *pc) { - json_t *prices; - - prices = json_array (); - GNUNET_assert (NULL != prices); - for (unsigned int i = 0; i<p->prices_length; i++) - GNUNET_assert (0 == - json_array_append_new (prices, - TALER_JSON_from_amount ( - &p->prices[i]))); - 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_incref ("description_i18n", - (json_t *) p->description_i18n)), - GNUNET_JSON_pack_allow_null ( - ( (0 != p->unit_quantity.integer) || - (0 != p->unit_quantity.fractional) ) - ? GNUNET_JSON_pack_string ("unit_quantity", - quantity_to_string (&p->unit_quantity)) - : GNUNET_JSON_pack_string ("dummy", - NULL) ), - /* Legacy */ - GNUNET_JSON_pack_allow_null ( - (0 == p->unit_quantity.fractional) - ? GNUNET_JSON_pack_uint64 ("quantity", - p->unit_quantity.integer) - : GNUNET_JSON_pack_string ("dummy", - NULL) ), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("unit", - p->unit)), - /* Deprecated, use prices! */ - GNUNET_JSON_pack_allow_null ( - TALER_JSON_pack_amount ("price", - 0 < p->prices_length - ? &p->prices[0] - : NULL)), - GNUNET_JSON_pack_array_steal ("prices", - prices), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("image", - p->image)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_array_incref ("taxes", - (json_t *) 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) -{ - struct TALER_MERCHANT_CommonTerms *ct = &input->common; + json_t *bj; json_t *details; json_t *products; - switch (ct->version) + bj = TALER_MERCHANT_base_terms_serialize (pc->base); + switch (base->version) { case TALER_MERCHANT_CONTRACT_VERSION_0: - details = json_from_contract_v0 (input); + details = json_from_contract_v0 (pc); goto success; case TALER_MERCHANT_CONTRACT_VERSION_1: - details = json_from_contract_v1 (input); + details = json_from_contract_v1 (pc); goto success; } @@ -470,29 +133,8 @@ success: } return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("version", - ct->version), - GNUNET_JSON_pack_string ("summary", - ct->summary), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ("summary_i18n", - ct->summary_i18n)), - GNUNET_JSON_pack_string ("order_id", - ct->order_id), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("public_reorder_url", - ct->public_reorder_url)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_url", - ct->fulfillment_url)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_message", - ct->fulfillment_message)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n", - ct->fulfillment_message_i18n)), - GNUNET_JSON_pack_array_steal ("products", - products), + GNUNET_JSON_pack_object_steal (NULL, + bj), GNUNET_JSON_pack_timestamp ("timestamp", ct->timestamp), GNUNET_JSON_pack_timestamp ("refund_deadline", @@ -509,41 +151,34 @@ success: "merchant", TALER_MERCHANT_metadata_to_json ( &input->common.merchant)), + GNUNET_JSON_pack_array_steal ("products", + products), GNUNET_JSON_pack_data_auto ("h_wire", &ct->h_wire), GNUNET_JSON_pack_string ("wire_method", ct->wire_method), GNUNET_JSON_pack_array_steal ("exchanges", ct->exchanges), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ("delivery_location", - ct->delivery_location)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_timestamp ("delivery_date", - ct->delivery_date)), - GNUNET_JSON_pack_allow_null ( - GNUNET_TIME_absolute_is_never (ct->max_pickup_time.abs_time) - ? GNUNET_JSON_pack_string ("dummy", - NULL) - : GNUNET_JSON_pack_timestamp ("max_pickup_time", - ct->max_pickup_time)), + GNUNET_JSON_pack_object_steal (NULL, + details)); +} + + +json_t * +TALER_MERCHANT_contract_serialize ( + const struct TALER_MERCHANT_Contract *input) +{ + json_t *pj; + + pj = TALER_MERCHANT_proto_contract_serialize (input->pc); + if (NULL == pj) + { + GNUNET_break (0); + return NULL; + } + return GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("nonce", input->nonce), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_time_rel ("auto_refund", - ct->auto_refund)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ("extra", - ct->extra)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_uint64 ("minimum_age", - ct->minimum_age)), - (0 == ct->default_money_pot) - ? GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("dummy", - NULL)) - : GNUNET_JSON_pack_uint64 ("default_money_pot", - ct->default_money_pot), GNUNET_JSON_pack_object_steal (NULL, - details)); + pj)); } diff --git a/src/util/meson.build b/src/util/meson.build @@ -15,18 +15,24 @@ endforeach libtalermerchantutil_SOURCES = [ 'amount_quantity.c', - 'common_terms_parse.c', + 'base_terms_parse.c', + 'base_terms_serialize.c', + 'contract_choice_parse.c', + 'contract_choice_serialize.c', 'contract_parse.c', 'contract_serialize.c', 'contract_version_parse.c', 'json.c', 'merchant_parse.c', 'mfa.c', + 'order_choice_parse.c', 'order_parse.c', 'os_installation.c', 'product_parse.c', + 'product_sold_serialize.c', 'template_parse.c', 'token_family_parse.c', + 'token_family_serialize.c', 'util.c', 'validators.c', 'value_kinds.c', diff --git a/src/util/order_choice_parse.c b/src/util/order_choice_parse.c @@ -0,0 +1,463 @@ +/* + This file is part of TALER + (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 + 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 src/util/order_choice_parse.c + * @brief shared logic for order choice parsing + * @author Iván Ávalos + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <stdbool.h> +#include <stdint.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> +#include "taler/taler_merchant_util.h" + + +enum GNUNET_GenericReturnValue +TALER_MERCHANT_parse_order_choice_input ( + json_t *root, + struct TALER_MERCHANT_OrderInput *input, + size_t index) +{ + const char *ename; + unsigned int eline; + struct GNUNET_JSON_Specification ispec[] = { + TALER_MERCHANT_json_spec_cit ("type", + &input->type), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + ispec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + switch (input->type) + { + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: + GNUNET_break (0); + break; + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("token_family_slug", + &input->details.token.token_family_slug), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("count", + &input->details.token.count), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'type' invalid in input #%u\n", + (unsigned int) index); + GNUNET_break_op (0); + return GNUNET_SYSERR; +} + + +enum GNUNET_GenericReturnValue +TALER_MERCHANT_parse_order_choice_output ( + json_t *root, + struct TALER_MERCHANT_OrderOutput *output, + size_t index) +{ + const char *ename; + unsigned int eline; + struct GNUNET_JSON_Specification ispec[] = { + TALER_MERCHANT_json_spec_cot ("type", + &output->type), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + ispec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + ispec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + switch (output->type) + { + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_break (0); + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("token_family_slug", + &output->details.token.token_family_slug), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint ("count", + &output->details.token.count), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("valid_at", + &output->details.token.valid_at), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; + } + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + { + const json_t *donau_urls = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("amount", + &output->details.donation_receipt.amount), + &output->details.donation_receipt.no_amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (root, + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + GNUNET_array_grow (output->details.donation_receipt.donau_urls, + output->details.donation_receipt.donau_urls_len, + json_array_size (donau_urls)); + + for (unsigned int i = 0; + i < output->details.donation_receipt.donau_urls_len; + i++) + { + const json_t *jurl; + + jurl = json_array_get (donau_urls, + i); + if (! json_is_string (jurl)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + output->details.donation_receipt.donau_urls[i] = + GNUNET_strdup (json_string_value (jurl)); + } + + return GNUNET_OK; + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'type' invalid in output #%u\n", + (unsigned int) index); + GNUNET_break_op (0); + return GNUNET_SYSERR; +} + + +/** + * Parse given JSON object to choices array. + * + * @param cls closure, pointer to array length + * @param root the json array representing the choices + * @param[out] ospec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_order_choices ( + void *cls, + json_t *root, + struct GNUNET_JSON_Specification *ospec) +{ + struct TALER_MERCHANT_OrderChoice **choices = ospec->ptr; + unsigned int *choices_len = cls; + + if (! json_is_array (root)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 == json_array_size (root)) + { + /* empty list of choices is not allowed */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *choices = NULL; + *choices_len = 0; + GNUNET_array_grow (*choices, + *choices_len, + json_array_size (root)); + + for (unsigned int i = 0; i < *choices_len; i++) + { + struct TALER_MERCHANT_OrderChoice *choice = &(*choices)[i]; + const json_t *jinputs = NULL; + const json_t *joutputs = NULL; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("amount", + &choice->amount), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("tip", + &choice->tip), + &choice->no_tip), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string_copy ("description", + &choice->description), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_copy ("description_i18n", + &choice->description_i18n), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("max_fee", + &choice->max_fee), + &choice->no_max_fee), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("inputs", + &jinputs), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("outputs", + &joutputs), + NULL), + GNUNET_JSON_spec_end () + }; + const char *ename; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (root, i), + spec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse %s at %u: %s\n", + spec[eline].field, + eline, + ename); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (! choice->no_tip) && + (GNUNET_OK != + TALER_amount_cmp_currency (&choice->amount, + &choice->tip)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Tip currency does not match amount currency in choice #%u\n", + i); + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (NULL != jinputs) + { + const json_t *jinput; + size_t idx; + + json_array_foreach ((json_t *) jinputs, idx, jinput) + { + struct TALER_MERCHANT_OrderInput input = { + .details.token.count = 1 + }; + + if (GNUNET_OK != + TALER_MERCHANT_parse_order_choice_input ((json_t *) jinput, + &input, + idx)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + switch (input.type) + { + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: + GNUNET_break_op (0); + return GNUNET_SYSERR; + case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: + /* Ignore inputs tokens with 'count' field set to 0 */ + if (0 == input.details.token.count) + continue; + break; + } + GNUNET_array_append (choice->inputs, + choice->inputs_len, + input); + } + } + + if (NULL != joutputs) + { + const json_t *joutput; + size_t idx; + json_array_foreach ((json_t *) joutputs, idx, joutput) + { + struct TALER_MERCHANT_OrderOutput output = { + .details.token.count = 1 + }; + + if (GNUNET_OK != + TALER_MERCHANT_parse_order_choice_output ((json_t *) joutput, + &output, + idx)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + switch (output.type) + { + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_break_op (0); + return GNUNET_SYSERR; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: + /* Ignore output tokens with 'count' field set to 0 */ + if (0 == output.details.token.count) + continue; + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + break; + } + GNUNET_array_append (choice->outputs, + choice->outputs_len, + output); + } + } + } + + return GNUNET_OK; +} + + +struct GNUNET_JSON_Specification +TALER_MERCHANT_spec_order_choices ( + const char *name, + struct TALER_MERCHANT_OrderChoice **choices, + unsigned int *choices_len) +{ + struct GNUNET_JSON_Specification ret = { + .cls = (void *) choices_len, + .parser = &parse_order_choices, + .field = name, + .ptr = choices, + }; + + return ret; +} + + +void +TALER_MERCHANT_order_choice_free ( + struct TALER_MERCHANT_OrderChoice *choice) +{ + for (unsigned int i = 0; i < choice->outputs_len; i++) + { + struct TALER_MERCHANT_OrderOutput *output = &choice->outputs[i]; + + switch (output->type) + { + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_break (0); + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: + break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + for (unsigned int j = 0; + j<output->details.donation_receipt.donau_urls_len; + j++) + GNUNET_free (output->details.donation_receipt.donau_urls[j]); + GNUNET_array_grow (output->details.donation_receipt.donau_urls, + output->details.donation_receipt.donau_urls_len, + 0); + break; +#if FUTURE + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN: + GNUNET_free (output->details.coin.exchange_url); + break; +#endif + } + } + GNUNET_free (choice->description); + if (NULL != choice->description_i18n) + { + json_decref (choice->description_i18n); + choice->description_i18n = NULL; + } + GNUNET_free (choice->inputs); + GNUNET_free (choice->outputs); +} diff --git a/src/util/order_parse.c b/src/util/order_parse.c @@ -30,439 +30,6 @@ #include "taler/taler_merchant_util.h" -enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_order_choice_input ( - json_t *root, - struct TALER_MERCHANT_OrderInput *input, - size_t index) -{ - const char *ename; - unsigned int eline; - struct GNUNET_JSON_Specification ispec[] = { - TALER_MERCHANT_json_spec_cit ("type", - &input->type), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - ispec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - ispec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - switch (input->type) - { - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: - GNUNET_break (0); - break; - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("token_family_slug", - &input->details.token.token_family_slug), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("count", - &input->details.token.count), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - return GNUNET_OK; - } - } - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'type' invalid in input #%u\n", - (unsigned int) index); - GNUNET_break_op (0); - return GNUNET_SYSERR; -} - - -enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_order_choice_output ( - json_t *root, - struct TALER_MERCHANT_OrderOutput *output, - size_t index) -{ - const char *ename; - unsigned int eline; - struct GNUNET_JSON_Specification ispec[] = { - TALER_MERCHANT_json_spec_cot ("type", - &output->type), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - ispec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - ispec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - switch (output->type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_break (0); - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("token_family_slug", - &output->details.token.token_family_slug), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint ("count", - &output->details.token.count), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("valid_at", - &output->details.token.valid_at), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - return GNUNET_OK; - } - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - { - const json_t *donau_urls = NULL; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &output->details.donation_receipt.amount), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (root, - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - GNUNET_array_grow (output->details.donation_receipt.donau_urls, - output->details.donation_receipt.donau_urls_len, - json_array_size (donau_urls)); - - for (unsigned int i = 0; - i < output->details.donation_receipt.donau_urls_len; - i++) - { - const json_t *jurl; - - jurl = json_array_get (donau_urls, - i); - if (! json_is_string (jurl)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - output->details.donation_receipt.donau_urls[i] = - GNUNET_strdup (json_string_value (jurl)); - } - - return GNUNET_OK; - } - } - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'type' invalid in output #%u\n", - (unsigned int) index); - GNUNET_break_op (0); - return GNUNET_SYSERR; -} - - -/** - * Parse given JSON object to choices array. - * - * @param cls closure, pointer to array length - * @param root the json array representing the choices - * @param[out] ospec where to write the data - * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error - */ -static enum GNUNET_GenericReturnValue -parse_order_choices ( - void *cls, - json_t *root, - struct GNUNET_JSON_Specification *ospec) -{ - struct TALER_MERCHANT_OrderChoice **choices = ospec->ptr; - unsigned int *choices_len = cls; - - if (! json_is_array (root)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 == json_array_size (root)) - { - /* empty list of choices is not allowed */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - *choices = NULL; - *choices_len = 0; - GNUNET_array_grow (*choices, - *choices_len, - json_array_size (root)); - - for (unsigned int i = 0; i < *choices_len; i++) - { - struct TALER_MERCHANT_OrderChoice *choice = &(*choices)[i]; - const json_t *jinputs = NULL; - const json_t *joutputs = NULL; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("amount", - &choice->amount), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("tip", - &choice->tip), - &choice->no_tip), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string_copy ("description", - &choice->description), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_copy ("description_i18n", - &choice->description_i18n), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("max_fee", - &choice->max_fee), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("inputs", - &jinputs), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("outputs", - &joutputs), - NULL), - GNUNET_JSON_spec_end () - }; - const char *ename; - unsigned int eline; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (root, i), - spec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse %s at %u: %s\n", - spec[eline].field, - eline, - ename); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if ( (! choice->no_tip) && - (GNUNET_OK != - TALER_amount_cmp_currency (&choice->amount, - &choice->tip)) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Tip currency does not match amount currency in choice #%u\n", - i); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (NULL != jinputs) - { - const json_t *jinput; - size_t idx; - - json_array_foreach ((json_t *) jinputs, idx, jinput) - { - struct TALER_MERCHANT_OrderInput input = { - .details.token.count = 1 - }; - - if (GNUNET_OK != - TALER_MERCHANT_parse_order_choice_input ((json_t *) jinput, - &input, - idx)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - switch (input.type) - { - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: - GNUNET_break_op (0); - return GNUNET_SYSERR; - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: - /* Ignore inputs tokens with 'count' field set to 0 */ - if (0 == input.details.token.count) - continue; - break; - } - GNUNET_array_append (choice->inputs, - choice->inputs_len, - input); - } - } - - if (NULL != joutputs) - { - const json_t *joutput; - size_t idx; - json_array_foreach ((json_t *) joutputs, idx, joutput) - { - struct TALER_MERCHANT_OrderOutput output = { - .details.token.count = 1 - }; - - if (GNUNET_OK != - TALER_MERCHANT_parse_order_choice_output ((json_t *) joutput, - &output, - idx)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - switch (output.type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_break_op (0); - return GNUNET_SYSERR; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - /* Ignore output tokens with 'count' field set to 0 */ - if (0 == output.details.token.count) - continue; - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - break; - } - GNUNET_array_append (choice->outputs, - choice->outputs_len, - output); - } - } - } - - return GNUNET_OK; -} - - -struct GNUNET_JSON_Specification -TALER_MERCHANT_spec_order_choices ( - const char *name, - struct TALER_MERCHANT_OrderChoice **choices, - unsigned int *choices_len) -{ - struct GNUNET_JSON_Specification ret = { - .cls = (void *) choices_len, - .parser = &parse_order_choices, - .field = name, - .ptr = choices, - }; - - return ret; -} - - -void -TALER_MERCHANT_order_choice_free ( - struct TALER_MERCHANT_OrderChoice *choice) -{ - for (unsigned int i = 0; i < choice->outputs_len; i++) - { - struct TALER_MERCHANT_OrderOutput *output = &choice->outputs[i]; - - switch (output->type) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: - GNUNET_break (0); - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - break; - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - for (unsigned int j = 0; - j<output->details.donation_receipt.donau_urls_len; - j++) - GNUNET_free (output->details.donation_receipt.donau_urls[j]); - GNUNET_array_grow (output->details.donation_receipt.donau_urls, - output->details.donation_receipt.donau_urls_len, - 0); - break; -#if FUTURE - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN: - GNUNET_free (output->details.coin.exchange_url); - break; -#endif - } - } - GNUNET_free (choice->description); - if (NULL != choice->description_i18n) - { - json_decref (choice->description_i18n); - choice->description_i18n = NULL; - } - GNUNET_free (choice->inputs); - GNUNET_free (choice->outputs); -} - - /** * Parse v0-specific fields of @a input JSON into @a order. * @@ -482,8 +49,10 @@ parse_order_v0 ( TALER_JSON_spec_amount_any ("tip", &order->details.v0.tip), &order->details.v0.no_tip), - TALER_JSON_spec_amount_any ("max_fee", - &order->details.v0.max_fee), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("max_fee", + &order->details.v0.max_fee), + &order->details.v0.no_max_fee), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; @@ -503,9 +72,10 @@ parse_order_v0 ( return GNUNET_SYSERR; } - if (GNUNET_OK != - TALER_amount_cmp_currency (&order->details.v0.max_fee, - &order->details.v0.brutto)) + if ( (! order->details.v0.no_max_fee) && + (GNUNET_OK != + TALER_amount_cmp_currency (&order->details.v0.max_fee, + &order->details.v0.brutto)) ) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -544,10 +114,6 @@ parse_order_v1 ( "choices", &order->details.v1.choices, &order->details.v1.choices_len), - TALER_MERCHANT_spec_token_families ( - "token_families", - &order->details.v1.token_authorities, - &order->details.v1.token_authorities_len), GNUNET_JSON_spec_end () }; @@ -581,14 +147,84 @@ TALER_MERCHANT_order_parse (json_t *input) = GNUNET_SYSERR; GNUNET_assert (NULL != input); - if (! TALER_MERCHANT_common_terms_parse (&order->common, - input)) + order->base = TALER_MERCHANT_base_terms_parse (input); + if (NULL == order->base) { GNUNET_break (0); GNUNET_free (order); return NULL; } - switch (order->common.version) + order->refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS; + order->wire_deadline = GNUNET_TIME_UNIT_FOREVER_TS; + { + json_t *products; + struct GNUNET_JSON_Specification espec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("products", + &products), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("timestamp", + &order->timestamp), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("refund_deadline", + &order->refund_deadline), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("pay_deadline", + &order->pay_deadline), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("wire_transfer_deadline", + &order->wire_deadline), + NULL), + GNUNET_JSON_spec_end () + }; + const char *ename; + unsigned int eline; + + res = GNUNET_JSON_parse (input, + espec, + &ename, + &eline); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse proto contract at field %s\n", + ename); + goto cleanup; + } + if (NULL != products) + { + order->products_len = json_array_size (products); + if (0 != order->products_len) + { + size_t i; + json_t *p; + + order->products = GNUNET_new_array ( + order->products_len, + struct TALER_MERCHANT_ProductSold); + json_array_foreach (products, i, p) + { + if (GNUNET_OK != + TALER_MERCHANT_parse_product_sold (p, + &order->products[i], + true)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse product at offset %u\n", + (unsigned int) i); + goto cleanup; + } + } + } + } + } + switch (order->base.version) { case TALER_MERCHANT_CONTRACT_VERSION_0: res = parse_order_v0 (input, @@ -638,6 +274,17 @@ TALER_MERCHANT_order_free ( GNUNET_free (order->details.v1.token_authorities); break; } - TALER_MERCHANT_common_terms_free (&order->common); + if (NULL != order->products) + { + for (size_t i = 0; i<order->products_len; i++) + TALER_MERCHANT_product_sold_free (&order->products[i]); + GNUNET_free (order->products); + order->products_len = NULL; + } + if (NULL != order->base) + { + TALER_MERCHANT_base_terms_free (order->base); + order->base = NULL; + } GNUNET_free (order); } diff --git a/src/util/product_sold_serialize.c b/src/util/product_sold_serialize.c @@ -0,0 +1,116 @@ +/* + This file is part of GNU Taler + 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 + 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 src/util/product_sold_serialize.c + * @brief shared logic for product sold serialization + * @author Iván Ávalos + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_common.h> +#include <taler/taler_json_lib.h> +#include <jansson.h> +#include "taler/taler_util.h" +#include "taler/taler_merchant_util.h" + + +/** + * Convert quantity @a q into a string for JSON serialization + * + * @param q quantity to convert + * @return formatted string + */ +static const char * +quantity_to_string (const struct TALER_MERCHANT_ProductQuantity *q) +{ + static char res[64]; + + TALER_MERCHANT_vk_format_fractional_string (TALER_MERCHANT_VK_QUANTITY, + q->integer, + q->fractional, + sizeof (res), + res); + return res; +} + + +json_t * +TALER_MERCHANT_product_sold_serialize ( + const struct TALER_MERCHANT_ProductSold *p) +{ + json_t *prices; + + prices = json_array (); + GNUNET_assert (NULL != prices); + for (unsigned int i = 0; i<p->prices_length; i++) + GNUNET_assert (0 == + json_array_append_new (prices, + TALER_JSON_from_amount ( + &p->prices[i]))); + 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_incref ("description_i18n", + (json_t *) p->description_i18n)), + GNUNET_JSON_pack_allow_null ( + ( (0 != p->unit_quantity.integer) || + (0 != p->unit_quantity.fractional) ) + ? GNUNET_JSON_pack_string ("unit_quantity", + quantity_to_string (&p->unit_quantity)) + : GNUNET_JSON_pack_string ("dummy", + NULL) ), + /* Legacy */ + GNUNET_JSON_pack_allow_null ( + (0 == p->unit_quantity.fractional) + ? GNUNET_JSON_pack_uint64 ("quantity", + p->unit_quantity.integer) + : GNUNET_JSON_pack_string ("dummy", + NULL) ), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("unit", + p->unit)), + /* Deprecated, use prices! */ + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("price", + 0 < p->prices_length + ? &p->prices[0] + : NULL)), + GNUNET_JSON_pack_array_steal ("prices", + prices), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("image", + p->image)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("taxes", + (json_t *) 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)); +} diff --git a/src/util/template_parse.c b/src/util/template_parse.c @@ -100,21 +100,25 @@ parse_template_inventory (const json_t *template_contract, { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("selected_all", - &out->details.inventory.selected_all), + GNUNET_JSON_spec_bool ( + "selected_all", + &out->details.inventory.selected_all), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_categories", - &out->details.inventory.selected_categories) + GNUNET_JSON_spec_array_const ( + "selected_categories", + &out->details.inventory.selected_categories) , NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_products", - &out->details.inventory.selected_products), + GNUNET_JSON_spec_array_const ( + "selected_products", + &out->details.inventory.selected_products), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("choose_one", - &out->details.inventory.choose_one), + GNUNET_JSON_spec_bool ( + "choose_one", + &out->details.inventory.choose_one), NULL), GNUNET_JSON_spec_end () }; diff --git a/src/util/token_family_serialize.c b/src/util/token_family_serialize.c @@ -0,0 +1,144 @@ +/* + This file is part of GNU Taler + 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 + 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 src/util/token_family_serialize.c + * @brief shared logic for token family serialization + * @author Iván Ávalos + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_common.h> +#include <taler/taler_json_lib.h> +#include <jansson.h> +#include "taler/taler_util.h" +#include "taler/taler_merchant_util.h" + + +/** + * Get JSON representation of contract token family key. + * + * @param[in] key contract token family key + * @return JSON representation of @a key; NULL on error + */ +static json_t * +json_from_token_family_key ( + const struct TALER_MERCHANT_ContractTokenFamilyKey *key) +{ + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("signature_validity_start", + key->valid_after), + GNUNET_JSON_pack_timestamp ("signature_validity_end", + key->valid_before), + TALER_JSON_pack_token_pub (NULL, + &key->pub)); +} + + +/** + * Get JSON representation of contract token family details. + * + * @param[in] family contract token family + * @return JSON representation of @a family->details; NULL on error + */ +static json_t * +json_from_token_family_details ( + const struct TALER_MERCHANT_ContractTokenFamily *family) +{ + switch (family->kind) + { + case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID: + break; + case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION: + { + json_t *trusted_domains; + + trusted_domains = json_array (); + GNUNET_assert (NULL != trusted_domains); + for (unsigned int i = 0; + i < family->details.subscription.trusted_domains_len; + i++) + GNUNET_assert (0 == + json_array_append_new ( + trusted_domains, + json_string ( + family->details.subscription.trusted_domains[i]))); + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("class", + "subscription"), + GNUNET_JSON_pack_array_steal ("trusted_domains", + trusted_domains)); + } + case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT: + { + json_t *expected_domains; + + expected_domains = json_array (); + GNUNET_assert (NULL != expected_domains); + for (unsigned int i = 0; + i < family->details.discount.expected_domains_len; + i++) + GNUNET_assert (0 == + json_array_append_new ( + expected_domains, + json_string ( + family->details.discount.expected_domains[i]))); + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("class", + "discount"), + GNUNET_JSON_pack_array_steal ("expected_domains", + expected_domains)); + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "unsupported token family kind %d", + family->kind); + GNUNET_assert (0); + return NULL; +} + + +json_t * +TALER_MERCHANT_json_from_token_family ( + const struct TALER_MERCHANT_ContractTokenFamily *family) +{ + json_t *keys; + + keys = json_array (); + GNUNET_assert (NULL != keys); + for (unsigned int i = 0; i < family->keys_len; i++) + GNUNET_assert (0 == json_array_append_new ( + keys, + json_from_token_family_key ( + &family->keys[i]))); + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("name", + family->name), + GNUNET_JSON_pack_string ("description", + family->description), + GNUNET_JSON_pack_object_incref ("description_i18n", + family->description_i18n), + GNUNET_JSON_pack_array_steal ("keys", + keys), + GNUNET_JSON_pack_object_steal ("details", + json_from_token_family_details (family)), + GNUNET_JSON_pack_bool ("critical", + family->critical)); +}