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:
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));
+}