commit 96ac05f7782c77663f43bb3a63cd36aa4a090c94
parent d1d992fdf54bd6b1a849272cae224f1ad7daf2a7
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 25 May 2026 21:45:28 +0200
Merge branch 'bNEW'
Diffstat:
28 files changed, 5321 insertions(+), 3616 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_get-orders-ORDER_ID.c b/src/backend/taler-merchant-httpd_get-orders-ORDER_ID.c
@@ -154,6 +154,28 @@ struct GetOrderData
struct TALER_MERCHANT_Contract *contract_terms;
/**
+ * Parsed proto contract terms, NULL when parsing failed.
+ * Careful, aliased with pc in @e contract_terms if @e contract_terms is
+ * not NULL!
+ */
+ struct TALER_MERCHANT_ProtoContract *pc;
+
+ /**
+ * Order of the payment we are checking. NULL when we have a contract.
+ */
+ json_t *order_json;
+
+ /**
+ * Parsed order, NULL when parsing failed.
+ */
+ struct TALER_MERCHANT_Order *order;
+
+ /**
+ * Common terms from @e order or @e contract
+ */
+ const struct TALER_MERCHANT_ContractBaseTerms *ct;
+
+ /**
* Total refunds granted for this payment. Only initialized
* if @e refunded is set to true.
*/
@@ -232,9 +254,9 @@ struct GetOrderData
bool generate_html;
/**
- * Did we parse the contract terms?
+ * Did we parse the order?
*/
- bool contract_parsed;
+ bool order_parsed;
/**
* Set to true if the refunds found in the DB have
@@ -310,15 +332,31 @@ suspend_god (struct GetOrderData *god)
{
json_decref (god->contract_terms_json);
god->contract_terms_json = NULL;
- god->contract_parsed = false;
+ }
+ if (NULL != god->order_json)
+ {
+ json_decref (god->order_json);
+ god->order_json = NULL;
+ god->order_parsed = false;
}
if (NULL != god->contract_terms)
{
TALER_MERCHANT_contract_free (god->contract_terms);
god->contract_terms = NULL;
+ god->pc = NULL;
}
+ if (NULL != god->pc)
+ {
+ TALER_MERCHANT_proto_contract_free (god->pc);
+ god->pc = NULL;
+ }
+ if (NULL != god->order)
+ {
+ TALER_MERCHANT_order_free (god->order);
+ god->order = NULL;
+ }
+ god->ct = NULL; /* ensure not dangling */
GNUNET_assert (! god->suspended);
- god->contract_parsed = false;
god->contract_match = false;
god->token_match = false;
god->contract_available = false;
@@ -351,6 +389,17 @@ god_cleanup (void *cls)
TALER_MERCHANT_contract_free (god->contract_terms);
god->contract_terms = NULL;
}
+ if (NULL != god->order_json)
+ {
+ json_decref (god->order_json);
+ god->order_json = NULL;
+ }
+ if (NULL != god->order)
+ {
+ TALER_MERCHANT_order_free (god->order);
+ god->order = NULL;
+ }
+ god->ct = NULL; /* ensure not dangling */
if (NULL != god->session_eh)
{
TALER_MERCHANTDB_event_listen_cancel (god->session_eh);
@@ -585,6 +634,7 @@ phase_lookup_terms (struct GetOrderData *god)
/* Convert order_id to h_contract_terms */
TALER_MERCHANTDB_preflight (TMH_db);
GNUNET_assert (NULL == god->contract_terms_json);
+ GNUNET_assert (NULL == god->order_json);
{
enum GNUNET_DB_QueryStatus qs;
@@ -689,9 +739,7 @@ phase_lookup_terms (struct GetOrderData *god)
god->order_id,
&db_claim_token,
&unused,
- (NULL == god->contract_terms_json)
- ? &god->contract_terms_json
- : NULL);
+ &god->order_json);
if (0 > qs)
{
/* single, read-only SQL statements should never cause
@@ -736,21 +784,61 @@ static void
phase_parse_contract (struct GetOrderData *god)
{
GNUNET_break (NULL == god->contract_terms);
- god->contract_terms = TALER_MERCHANT_contract_parse (
- god->contract_terms_json,
- true);
-
- if (NULL == god->contract_terms)
+ GNUNET_break (NULL == god->order);
+ if (NULL != god->contract_terms_json)
{
- phase_fail (god,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
- god->order_id);
- return;
+ if (NULL ==
+ json_object_get (god->contract_terms_json,
+ "nonce"))
+ {
+ god->pc = TALER_MERCHANT_proto_contract_parse (
+ god->contract_terms_json);
+ if (NULL == god->pc)
+ {
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ god->order_id);
+ return;
+ }
+ god->ct = god->pc->base;
+ }
+ else
+ {
+ god->contract_terms = TALER_MERCHANT_contract_parse (
+ god->contract_terms_json);
+ if (NULL == god->contract_terms)
+ {
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ god->order_id);
+ return;
+ }
+ god->pc = god->contract_terms->pc;
+ god->ct = god->contract_terms->pc->base;
+ }
}
- god->contract_parsed = true;
+ if (NULL != god->order_json)
+ {
+ god->order = TALER_MERCHANT_order_parse (
+ god->order_json);
+ if (NULL == god->order)
+ {
+ phase_fail (god,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ god->order_id);
+ return;
+ }
+ god->order_parsed = true;
+ god->ct = god->order->base;
+ }
+ GNUNET_assert ( (NULL != god->order) ||
+ (NULL != god->pc) );
+
if ( (NULL != god->session_id) &&
- (NULL != god->contract_terms->fulfillment_url) &&
+ (NULL != god->ct->fulfillment_url) &&
(NULL == god->session_eh) )
{
struct TMH_SessionEventP session_eh = {
@@ -765,8 +853,8 @@ phase_parse_contract (struct GetOrderData *god)
GNUNET_CRYPTO_hash (god->session_id,
strlen (god->session_id),
&session_eh.h_session_id);
- GNUNET_CRYPTO_hash (god->contract_terms->fulfillment_url,
- strlen (god->contract_terms->fulfillment_url),
+ GNUNET_CRYPTO_hash (god->ct->fulfillment_url,
+ strlen (god->ct->fulfillment_url),
&session_eh.h_fulfillment_url);
god->session_eh
= TALER_MERCHANTDB_event_listen (
@@ -821,11 +909,10 @@ phase_check_client_access (struct GetOrderData *god)
if (! (god->token_match ||
god->contract_match) )
{
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Neither claim token nor contract matched\n");
/* Client has no rights to this order */
- if (NULL == god->contract_terms->public_reorder_url)
+ if (NULL == god->ct->public_reorder_url)
{
/* We cannot give the client a new order, just fail */
if (! GNUNET_is_zero (&god->h_contract_terms))
@@ -872,7 +959,7 @@ phase_check_client_access (struct GetOrderData *god)
MHD_add_response_header (
reply,
MHD_HTTP_HEADER_LOCATION,
- god->contract_terms->public_reorder_url));
+ god->ct->public_reorder_url));
ret = MHD_queue_response (god->sc.con,
MHD_HTTP_FOUND,
reply);
@@ -888,7 +975,7 @@ phase_check_client_access (struct GetOrderData *god)
MHD_HTTP_ACCEPTED,
GNUNET_JSON_pack_string (
"public_reorder_url",
- god->contract_terms->public_reorder_url)));
+ god->ct->public_reorder_url)));
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -909,15 +996,22 @@ get_order_summary (const struct GetOrderData *god)
{
const char *language_pattern;
const char *ret;
+ json_t *terms;
- language_pattern = MHD_lookup_connection_value (god->sc.con,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL != god->contract_terms_json)
+ terms = god->contract_terms_json;
+ else
+ terms = god->order_json;
+ language_pattern = MHD_lookup_connection_value (
+ god->sc.con,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
if (NULL == language_pattern)
language_pattern = "en";
- ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms_json,
- language_pattern,
- "summary"));
+ ret = json_string_value (TALER_JSON_extract_i18n (
+ terms,
+ language_pattern,
+ "summary"));
if (NULL == ret)
{
/* Upon order creation (and insertion into the database), the presence
@@ -958,7 +1052,6 @@ send_pay_request (struct GetOrderData *god,
suspend_god (god);
return true;
}
-
/* Check if resource_id has been paid for in the same session
* with another order_id.
*/
@@ -995,11 +1088,10 @@ send_pay_request (struct GetOrderData *god,
{
struct MHD_Response *reply;
- GNUNET_assert (NULL != god->contract_terms->fulfillment_url);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Redirecting to already paid order %s via fulfillment URL %s\n",
already_paid_order_id,
- god->contract_terms->fulfillment_url);
+ god->ct->fulfillment_url);
reply = MHD_create_response_from_buffer (0,
NULL,
MHD_RESPMEM_PERSISTENT);
@@ -1014,7 +1106,7 @@ send_pay_request (struct GetOrderData *god,
MHD_add_response_header (
reply,
MHD_HTTP_HEADER_LOCATION,
- god->contract_terms->fulfillment_url));
+ god->ct->fulfillment_url));
{
ret = MHD_queue_response (god->sc.con,
MHD_HTTP_FOUND,
@@ -1080,7 +1172,7 @@ send_pay_request (struct GetOrderData *god,
taler_pay_uri),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_url",
- god->contract_terms->fulfillment_url)),
+ god->ct->fulfillment_url)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("already_paid_order_id",
already_paid_order_id)));
@@ -1137,7 +1229,7 @@ static bool
phase_redirect_to_paid_order (struct GetOrderData *god)
{
if ( (NULL != god->session_id) &&
- (NULL != god->contract_terms->fulfillment_url) )
+ (NULL != god->ct->fulfillment_url) )
{
/* Check if client paid for this fulfillment article
already within this session, but using a different
@@ -1153,11 +1245,11 @@ phase_redirect_to_paid_order (struct GetOrderData *god)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Running re-purchase detection for %s/%s\n",
god->session_id,
- god->contract_terms->fulfillment_url);
+ god->ct->fulfillment_url);
qs = TALER_MERCHANTDB_lookup_order_by_fulfillment (
TMH_db,
god->hc->instance->settings.id,
- god->contract_terms->fulfillment_url,
+ god->ct->fulfillment_url,
god->session_id,
TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase,
&already_paid_order_id);
@@ -1304,11 +1396,12 @@ phase_check_refunded (struct GetOrderData *god)
struct TALER_Amount refund_amount;
const char *refund_currency;
- switch (god->contract_terms->version)
+ GNUNET_assert (NULL != god->contract_terms);
+ switch (god->ct->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
- refund_amount = god->contract_terms->details.v0.brutto;
- refund_currency = god->contract_terms->details.v0.brutto.currency;
+ refund_amount = god->pc->details.v0.brutto;
+ refund_currency = god->pc->details.v0.brutto.currency;
break;
case TALER_MERCHANT_CONTRACT_VERSION_1:
if (god->choice_index < 0)
@@ -1318,11 +1411,12 @@ phase_check_refunded (struct GetOrderData *god)
return false;
}
GNUNET_assert (god->choice_index <
- god->contract_terms->details.v1.choices_len);
- refund_currency = god->contract_terms->details.v1.choices[god->choice_index]
- .amount.currency;
- GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (refund_currency,
- &refund_amount));
+ god->pc->details.v1.choices_len);
+ refund_currency = god->pc->details.v1.choices[
+ god->choice_index].amount.currency;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (refund_currency,
+ &refund_amount));
break;
default:
{
@@ -1483,8 +1577,7 @@ phase_return_status (struct GetOrderData *god)
MHD_HTTP_OK,
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_url",
- god->contract_terms->fulfillment_url
- )),
+ god->ct->fulfillment_url)),
GNUNET_JSON_pack_bool ("refunded",
god->refunded),
GNUNET_JSON_pack_bool ("refund_pending",
@@ -1502,7 +1595,8 @@ phase_return_status (struct GetOrderData *god)
char *uri;
GNUNET_assert (NULL != god->contract_terms_json);
- uri = make_taler_refund_uri (god->contract_terms->merchant_base_url,
+ GNUNET_assert (NULL != god->contract_terms);
+ uri = make_taler_refund_uri (god->pc->merchant_base_url,
god->order_id);
if (NULL == uri)
{
@@ -1565,7 +1659,9 @@ phase_return_status (struct GetOrderData *god)
context = GNUNET_JSON_PACK (
GNUNET_JSON_pack_object_incref ("contract_terms",
- god->contract_terms_json),
+ NULL != god->contract_terms_json
+ ? god->contract_terms_json
+ : god->order_json),
GNUNET_JSON_pack_string ("order_summary",
get_order_summary (god)),
TALER_JSON_pack_amount ("refund_amount",
diff --git a/src/backend/taler-merchant-httpd_get-private-orders-ORDER_ID.c b/src/backend/taler-merchant-httpd_get-private-orders-ORDER_ID.c
@@ -263,6 +263,33 @@ struct GetOrderRequestContext
struct TALER_MERCHANT_Contract *contract_terms;
/**
+ * Proto-contract. Be careful: do NOT free this
+ * if @e contract_terms is not NULL!
+ */
+ struct TALER_MERCHANT_ProtoContract *pc;
+
+ /**
+ * Order terms of the payment we are checking. NULL when we have
+ * a contract.
+ */
+ json_t *order_json;
+
+ /**
+ * Parsed order details, NULL when parsing failed or we have a contract.
+ */
+ struct TALER_MERCHANT_Order *order;
+
+ /**
+ * Common terms of @e order and @e contract.
+ */
+ const struct TALER_MERCHANT_ContractBaseTerms *ct;
+
+ /**
+ * Timestamp of the contract or order.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
* Claim token of the order.
*/
struct TALER_ClaimTokenP claim_token;
@@ -515,12 +542,32 @@ gorc_cleanup (void *cls)
}
if (NULL != gorc->contract_terms_json)
+ {
json_decref (gorc->contract_terms_json);
+ gorc->contract_terms_json = NULL;
+ }
+ if (NULL != gorc->order_json)
+ {
+ json_decref (gorc->order_json);
+ gorc->order_json = NULL;
+ }
if (NULL != gorc->contract_terms)
{
TALER_MERCHANT_contract_free (gorc->contract_terms);
gorc->contract_terms = NULL;
+ gorc->pc = NULL; /* was an alias! */
+ }
+ if (NULL != gorc->pc)
+ {
+ TALER_MERCHANT_proto_contract_free (gorc->pc);
+ gorc->pc = NULL;
}
+ if (NULL != gorc->order)
+ {
+ TALER_MERCHANT_order_free (gorc->order);
+ gorc->order = NULL;
+ }
+ gorc->ct = NULL; /* avoid dangling pointer */
if (NULL != gorc->wire_details)
json_decref (gorc->wire_details);
if (NULL != gorc->refund_details)
@@ -595,7 +642,7 @@ phase_init (struct GetOrderRequestContext *gorc)
&resume_by_event,
gorc);
if ( (NULL != gorc->session_id) &&
- (NULL != gorc->contract_terms->fulfillment_url) )
+ (NULL != gorc->ct->fulfillment_url) )
{
struct TMH_SessionEventP session_eh = {
.header.size = htons (sizeof (session_eh)),
@@ -609,8 +656,8 @@ phase_init (struct GetOrderRequestContext *gorc)
GNUNET_CRYPTO_hash (gorc->session_id,
strlen (gorc->session_id),
&session_eh.h_session_id);
- GNUNET_CRYPTO_hash (gorc->contract_terms->fulfillment_url,
- strlen (gorc->contract_terms->fulfillment_url),
+ GNUNET_CRYPTO_hash (gorc->ct->fulfillment_url,
+ strlen (gorc->ct->fulfillment_url),
&session_eh.h_fulfillment_url);
gorc->session_eh
= TALER_MERCHANTDB_event_listen (
@@ -743,13 +790,13 @@ phase_parse_contract (struct GetOrderRequestContext *gorc)
{
struct TMH_HandlerContext *hc = gorc->hc;
- if (NULL == gorc->contract_terms)
+ if ( (NULL == gorc->order) &&
+ (NULL != gorc->order_json) )
{
- gorc->contract_terms = TALER_MERCHANT_contract_parse (
- gorc->contract_terms_json,
- true);
+ gorc->order = TALER_MERCHANT_order_parse (
+ gorc->order_json);
- if (NULL == gorc->contract_terms)
+ if (NULL == gorc->order)
{
GNUNET_break (0);
phase_end (gorc,
@@ -760,18 +807,70 @@ phase_parse_contract (struct GetOrderRequestContext *gorc)
hc->infix));
return;
}
+ gorc->ct = gorc->order->base;
+ gorc->timestamp = gorc->order->timestamp;
+ }
+ if ( (NULL == gorc->contract_terms) &&
+ (NULL != gorc->contract_terms_json) )
+ {
+ if (NULL ==
+ json_object_get (gorc->contract_terms_json,
+ "nonce"))
+ {
+ /* only have a proto contract */
+ gorc->pc = TALER_MERCHANT_proto_contract_parse (
+ gorc->contract_terms_json);
+
+ if (NULL == gorc->pc)
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ hc->infix));
+ return;
+ }
+ gorc->ct = gorc->pc->base;
+ gorc->timestamp = gorc->pc->timestamp;
+ }
+ else
+ {
+ gorc->contract_terms = TALER_MERCHANT_contract_parse (
+ gorc->contract_terms_json);
+ gorc->pc = gorc->contract_terms->pc;
+ if (NULL == gorc->contract_terms)
+ {
+ GNUNET_break (0);
+ phase_end (gorc,
+ TALER_MHD_reply_with_error (
+ gorc->sc.con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ hc->infix));
+ return;
+ }
+ gorc->ct = gorc->contract_terms->pc->base;
+ gorc->timestamp = gorc->contract_terms->pc->timestamp;
+ }
}
- switch (gorc->contract_terms->version)
+ switch (gorc->ct->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
- gorc->contract_amount = gorc->contract_terms->details.v0.brutto;
+ gorc->contract_amount
+ = (NULL != gorc->pc)
+ ? gorc->pc->details.v0.brutto
+ : gorc->order->details.v0.brutto;
break;
case TALER_MERCHANT_CONTRACT_VERSION_1:
if (gorc->choice_index >= 0)
{
if (gorc->choice_index >=
- gorc->contract_terms->details.v1.choices_len)
+ ( (NULL != gorc->pc)
+ ? gorc->pc->details.v1.choices_len
+ : gorc->order->details.v1.choices_len) )
{
GNUNET_break (0);
phase_end (gorc,
@@ -782,17 +881,17 @@ phase_parse_contract (struct GetOrderRequestContext *gorc)
NULL));
return;
}
-
gorc->contract_amount =
- gorc->contract_terms->details.v1.choices[gorc->choice_index].amount;
+ (NULL != gorc->pc)
+ ? gorc->pc->details.v1.choices[gorc->choice_index].amount
+ : gorc->order->details.v1.choices[gorc->choice_index].amount;
}
else
{
GNUNET_break (gorc->order_only);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Choice index %i for order %s is invalid or not yet available",
- gorc->choice_index,
- gorc->contract_terms->order_id);
+ "Choice index %i is invalid",
+ gorc->choice_index);
}
break;
default:
@@ -808,7 +907,7 @@ phase_parse_contract (struct GetOrderRequestContext *gorc)
}
}
- if ( (! gorc->order_only) &&
+ if ( (NULL != gorc->contract_terms) &&
(GNUNET_OK !=
TALER_JSON_contract_hash (gorc->contract_terms_json,
&gorc->h_contract_terms)) )
@@ -821,8 +920,10 @@ phase_parse_contract (struct GetOrderRequestContext *gorc)
NULL));
return;
}
- GNUNET_assert (NULL != gorc->contract_terms_json);
- GNUNET_assert (NULL != gorc->contract_terms);
+ GNUNET_assert ( (NULL != gorc->contract_terms_json) ||
+ (NULL != gorc->order_json) );
+ GNUNET_assert ( (NULL != gorc->pc) ||
+ (NULL != gorc->order) );
gorc->phase++;
}
@@ -982,7 +1083,7 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc)
json_t *reply;
if ( (gorc->paid) ||
- (NULL == gorc->contract_terms->fulfillment_url) ||
+ (NULL == gorc->ct->fulfillment_url) ||
(NULL == gorc->session_id) )
{
/* Repurchase cannot apply */
@@ -992,11 +1093,11 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Running re-purchase detection for %s/%s\n",
gorc->session_id,
- gorc->contract_terms->fulfillment_url);
+ gorc->ct->fulfillment_url);
qs = TALER_MERCHANTDB_lookup_order_by_fulfillment (
TMH_db,
hc->instance->settings.id,
- gorc->contract_terms->fulfillment_url,
+ gorc->ct->fulfillment_url,
gorc->session_id,
TALER_EXCHANGE_YNA_NO !=
gorc->allow_refunded_for_repurchase,
@@ -1018,7 +1119,7 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"No already paid order for %s/%s\n",
gorc->session_id,
- gorc->contract_terms->fulfillment_url);
+ gorc->ct->fulfillment_url);
gorc->phase++;
return;
}
@@ -1062,7 +1163,7 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc)
GNUNET_JSON_pack_string ("already_paid_order_id",
already_paid_order_id),
GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
- gorc->contract_terms->fulfillment_url),
+ gorc->ct->fulfillment_url),
/* undefined for unpaid v1 contracts */
GNUNET_JSON_pack_allow_null (
TALER_JSON_pack_amount ("total_amount",
@@ -1072,11 +1173,13 @@ phase_check_repurchase (struct GetOrderRequestContext *gorc)
GNUNET_JSON_pack_object_incref ("proto_contract_terms",
gorc->contract_terms_json),
GNUNET_JSON_pack_string ("summary",
- gorc->contract_terms->summary),
+ gorc->ct->summary),
GNUNET_JSON_pack_timestamp ("pay_deadline",
- gorc->contract_terms->pay_deadline),
+ NULL != gorc->pc
+ ? gorc->pc->pay_deadline
+ : gorc->order->pay_deadline),
GNUNET_JSON_pack_timestamp ("creation_time",
- gorc->contract_terms->timestamp));
+ gorc->timestamp));
GNUNET_free (order_status_url);
GNUNET_free (taler_pay_uri);
@@ -1168,9 +1271,9 @@ phase_unpaid_finish (struct GetOrderRequestContext *gorc)
TALER_JSON_pack_amount ("total_amount",
&gorc->contract_amount)),
GNUNET_JSON_pack_string ("summary",
- gorc->contract_terms->summary),
+ gorc->ct->summary),
GNUNET_JSON_pack_timestamp ("creation_time",
- gorc->contract_terms->timestamp));
+ gorc->timestamp));
check_reply (gorc,
reply);
json_decref (reply);
@@ -1584,7 +1687,7 @@ phase_check_local_transfers (struct GetOrderRequestContext *gorc)
TMH_notify_order_change (hc->instance,
TMH_OSF_PAID
| TMH_OSF_WIRED,
- gorc->contract_terms->timestamp,
+ gorc->timestamp,
gorc->order_serial);
}
}
@@ -1630,7 +1733,7 @@ phase_reply_result (struct GetOrderRequestContext *gorc)
{
GNUNET_break (GNUNET_YES ==
TALER_amount_is_zero (&gorc->contract_amount));
- gorc->last_payment = gorc->contract_terms->timestamp;
+ gorc->last_payment = gorc->timestamp;
}
{
json_t *reply;
diff --git a/src/backend/taler-merchant-httpd_get-private-orders.c b/src/backend/taler-merchant-httpd_get-private-orders.c
@@ -462,7 +462,7 @@ add_order (void *cls,
struct GNUNET_TIME_Timestamp creation_time)
{
struct TMH_PendingOrder *po = cls;
- json_t *contract_terms = NULL;
+ json_t *terms = NULL;
struct TALER_PrivateContractHashP h_contract_terms;
enum GNUNET_DB_QueryStatus qs;
char *order_id = NULL;
@@ -470,6 +470,7 @@ add_order (void *cls,
bool paid;
bool wired;
struct TALER_MERCHANT_Contract *contract = NULL;
+ struct TALER_MERCHANT_Order *order = NULL;
int16_t choice_index = -1;
struct ProcessRefundsClosure prc = {
.ec = TALER_EC_NONE
@@ -478,6 +479,7 @@ add_order (void *cls,
char amount_buf[128];
char refund_buf[128];
char pending_buf[128];
+ const struct TALER_MERCHANT_ContractBaseTerms *ct = NULL;
/* Bail early if we already have an error */
if (TALER_EC_NONE != po->result)
@@ -522,7 +524,7 @@ add_order (void *cls,
po->instance_id,
order_id,
NULL,
- &contract_terms,
+ &terms,
&os,
&paid,
&wired,
@@ -530,7 +532,17 @@ add_order (void *cls,
NULL,
&choice_index);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
GNUNET_break (os == order_serial);
+ contract = TALER_MERCHANT_contract_parse (terms);
+ if (NULL == contract)
+ {
+ GNUNET_break (0);
+ po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
+ goto cleanup;
+ }
+ ct = contract->pc->base;
+ }
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
@@ -544,7 +556,18 @@ add_order (void *cls,
order_id,
NULL,
&unused,
- &contract_terms);
+ &terms);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ order = TALER_MERCHANT_order_parse (terms);
+ if (NULL == order)
+ {
+ GNUNET_break (0);
+ po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
+ goto cleanup;
+ }
+ ct = order->base;
+ }
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
@@ -560,30 +583,23 @@ add_order (void *cls,
goto cleanup;
}
- contract = TALER_MERCHANT_contract_parse (contract_terms,
- true);
- if (NULL == contract)
- {
- GNUNET_break (0);
- po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
- goto cleanup;
- }
if (paid)
{
const struct TALER_Amount *brutto;
- switch (contract->version)
+ GNUNET_assert (NULL != contract);
+ switch (ct->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
- brutto = &contract->details.v0.brutto;
+ brutto = &contract->pc->details.v0.brutto;
break;
case TALER_MERCHANT_CONTRACT_VERSION_1:
{
struct TALER_MERCHANT_ContractChoice *choice
- = &contract->details.v1.choices[choice_index];
+ = &contract->pc->details.v1.choices[choice_index];
- GNUNET_assert (choice_index < contract->details.v1.choices_len);
+ GNUNET_assert (choice_index < contract->pc->details.v1.choices_len);
brutto = &choice->amount;
}
break;
@@ -617,17 +633,20 @@ add_order (void *cls,
}
if (0 > TALER_amount_cmp (&prc.total_refund_amount,
brutto) &&
- GNUNET_TIME_absolute_is_future (contract->refund_deadline.abs_time))
+ GNUNET_TIME_absolute_is_future (
+ contract->pc->refund_deadline.abs_time))
refundable = true;
}
/* compute amount totals */
amount = NULL;
- switch (contract->version)
+ switch (ct->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
{
- amount = &contract->details.v0.brutto;
+ amount = (NULL != contract)
+ ? &contract->pc->details.v0.brutto
+ : &order->details.v0.brutto;
if (TALER_amount_is_zero (amount) &&
(po->of.wired != TALER_EXCHANGE_YNA_ALL) )
@@ -659,11 +678,12 @@ add_order (void *cls,
case TALER_MERCHANT_CONTRACT_VERSION_1:
if (-1 == choice_index)
choice_index = 0; /* default choice */
- GNUNET_assert (choice_index < contract->details.v1.choices_len);
+ if (NULL != contract)
{
struct TALER_MERCHANT_ContractChoice *choice
- = &contract->details.v1.choices[choice_index];
+ = &contract->pc->details.v1.choices[choice_index];
+ GNUNET_assert (choice_index < contract->pc->details.v1.choices_len);
amount = &choice->amount;
/* Accumulate order total */
accumulate_total (po,
@@ -680,6 +700,19 @@ add_order (void *cls,
goto cleanup;
}
}
+ else
+ {
+ struct TALER_MERCHANT_OrderChoice *choice
+ = &order->details.v1.choices[choice_index];
+
+ GNUNET_assert (choice_index < order->details.v1.choices_len);
+ amount = &choice->amount;
+ /* Accumulate order total */
+ accumulate_total (po,
+ amount);
+ if (TALER_EC_NONE != po->result)
+ goto cleanup;
+ }
break;
default:
GNUNET_break (0);
@@ -709,7 +742,7 @@ add_order (void *cls,
po->pa,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("order_id",
- contract->order_id),
+ order_id),
GNUNET_JSON_pack_uint64 ("row_id",
order_serial),
GNUNET_JSON_pack_timestamp ("timestamp",
@@ -729,7 +762,7 @@ add_order (void *cls,
? &prc.pending_refund_amount
: NULL)),
GNUNET_JSON_pack_string ("summary",
- contract->summary),
+ ct->summary),
GNUNET_JSON_pack_bool ("refundable",
refundable),
GNUNET_JSON_pack_bool ("paid",
@@ -737,7 +770,7 @@ add_order (void *cls,
break;
case POF_CSV:
{
- size_t len = strlen (contract->summary);
+ size_t len = strlen (ct->summary);
size_t wpos = 0;
char *esummary;
struct tm *tm;
@@ -747,16 +780,16 @@ add_order (void *cls,
esummary = GNUNET_malloc (2 * len + 1);
for (size_t off = 0; off<len; off++)
{
- if ('"' == contract->summary[off])
+ if ('"' == ct->summary[off])
esummary[wpos++] = '"';
- esummary[wpos++] = contract->summary[off];
+ esummary[wpos++] = ct->summary[off];
}
t = GNUNET_TIME_timestamp_to_s (creation_time);
tm = localtime (&t);
GNUNET_buffer_write_fstr (
&po->csv,
"%s,%llu,%04u-%02u-%02u,%02u:%02u (%s),%llu,%s,%s,%s,\"%s\",%s,%s\r\n",
- contract->order_id,
+ order_id,
(unsigned long long) order_serial,
tm->tm_year + 1900,
tm->tm_mon + 1,
@@ -776,7 +809,7 @@ add_order (void *cls,
}
case POF_XML:
{
- char *esummary = TALER_escape_xml (contract->summary);
+ char *esummary = TALER_escape_xml (ct->summary);
char creation_time_s[128];
const struct tm *tm;
time_t tt;
@@ -797,7 +830,7 @@ add_order (void *cls,
"<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
"<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>"
"</Row>\n",
- contract->order_id,
+ order_id,
creation_time_s,
amount_buf,
paid ? refund_buf : "",
@@ -810,8 +843,13 @@ add_order (void *cls,
} /* end switch po->format */
cleanup:
- json_decref (contract_terms);
+ json_decref (terms);
GNUNET_free (order_id);
+ if (NULL != order)
+ {
+ TALER_MERCHANT_order_free (order);
+ order = NULL;
+ }
if (NULL != contract)
{
TALER_MERCHANT_contract_free (contract);
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c
@@ -1059,7 +1059,7 @@ batch_deposit_transaction (
dr->details.ok.deposit_timestamp,
&pc->check_contract.h_contract_terms,
eg->exchange_url,
- pc->check_contract.contract_terms->wire_deadline,
+ pc->check_contract.contract_terms->pc->wire_deadline,
&dr->details.ok.accumulated_total_without_fee,
&eg->wire_fee,
&pc->check_contract.wm->h_wire,
@@ -1096,7 +1096,7 @@ batch_deposit_transaction (
&dc->deposit_fee,
&dc->refund_fee,
GNUNET_TIME_absolute_add (
- pc->check_contract.contract_terms->wire_deadline.abs_time,
+ pc->check_contract.contract_terms->pc->wire_deadline.abs_time,
GNUNET_TIME_randomize (GNUNET_TIME_UNIT_MINUTES)));
if (qs < 0)
return qs;
@@ -1220,8 +1220,8 @@ notify_kyc_required (const struct ExchangeGroup *eg)
char *extra;
hws = GNUNET_STRINGS_data_to_string_alloc (
- &eg->pc->check_contract.contract_terms->h_wire,
- sizeof (eg->pc->check_contract.contract_terms->h_wire));
+ &eg->pc->check_contract.contract_terms->pc->h_wire,
+ sizeof (eg->pc->check_contract.contract_terms->pc->h_wire));
GNUNET_asprintf (&extra,
"%s %s",
hws,
@@ -1413,16 +1413,24 @@ do_batch_deposits (struct ExchangeGroup *eg)
all coins are done. */
{
struct TALER_EXCHANGE_DepositContractDetail dcd = {
- .wire_deadline = pc->check_contract.contract_terms->wire_deadline,
- .merchant_payto_uri = pc->check_contract.wm->payto_uri,
+ .wire_deadline
+ = pc->check_contract.contract_terms->pc->wire_deadline,
+ .merchant_payto_uri
+ = pc->check_contract.wm->payto_uri,
.extra_wire_subject_metadata
= pc->check_contract.wm->extra_wire_subject_metadata,
- .wire_salt = pc->check_contract.wm->wire_salt,
- .h_contract_terms = pc->check_contract.h_contract_terms,
- .wallet_data_hash = pc->parse_wallet_data.h_wallet_data,
- .wallet_timestamp = pc->check_contract.contract_terms->timestamp,
- .merchant_pub = hc->instance->merchant_pub,
- .refund_deadline = pc->check_contract.contract_terms->refund_deadline
+ .wire_salt
+ = pc->check_contract.wm->wire_salt,
+ .h_contract_terms
+ = pc->check_contract.h_contract_terms,
+ .wallet_data_hash
+ = pc->parse_wallet_data.h_wallet_data,
+ .wallet_timestamp
+ = pc->check_contract.contract_terms->pc->timestamp,
+ .merchant_pub
+ = hc->instance->merchant_pub,
+ .refund_deadline
+ = pc->check_contract.contract_terms->pc->refund_deadline
};
/* Collect up to TALER_MAX_COINS eligible coins for this batch */
struct TALER_EXCHANGE_CoinDepositDetail cdds[group_size];
@@ -1674,7 +1682,7 @@ process_pay_with_keys (
is_age_restricted_denom = (0 != denom_details->key.age_mask.bits);
if (is_age_restricted_denom &&
- (0 < pc->check_contract.contract_terms->minimum_age))
+ (0 < pc->check_contract.contract_terms->pc->base->minimum_age))
{
/* Minimum age given and restricted coin provided: We need to verify the
* minimum age */
@@ -1698,7 +1706,7 @@ process_pay_with_keys (
if (GNUNET_OK !=
TALER_age_commitment_verify (
&dc->age_commitment,
- pc->check_contract.contract_terms->minimum_age,
+ pc->check_contract.contract_terms->pc->base->minimum_age,
&dc->minimum_age_sig))
code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED;
AGE_FAIL:
@@ -1929,11 +1937,11 @@ phase_success_response (struct PayContext *pc)
/* Build the response */
pos_confirmation = (NULL == pc->check_contract.pos_key)
? NULL
- : TALER_build_pos_confirmation (pc->check_contract.pos_key,
- pc->check_contract.pos_alg,
- &pc->validate_tokens.brutto,
- pc->check_contract.contract_terms->timestamp
- );
+ : TALER_build_pos_confirmation (
+ pc->check_contract.pos_key,
+ pc->check_contract.pos_alg,
+ &pc->validate_tokens.brutto,
+ pc->check_contract.contract_terms->pc->timestamp);
pay_end (pc,
TALER_MHD_REPLY_JSON_PACK (
pc->connection,
@@ -1996,7 +2004,7 @@ phase_payment_notification (struct PayContext *pc)
0);
}
if ( (NULL != pc->parse_pay.session_id) &&
- (NULL != pc->check_contract.contract_terms->fulfillment_url) )
+ (NULL != pc->check_contract.contract_terms->pc->base->fulfillment_url) )
{
struct TMH_SessionEventP session_eh = {
.header.size = htons (sizeof (session_eh)),
@@ -2007,14 +2015,14 @@ phase_payment_notification (struct PayContext *pc)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Notifying clients about session change to %s for %s\n",
pc->parse_pay.session_id,
- pc->check_contract.contract_terms->fulfillment_url);
+ pc->check_contract.contract_terms->pc->base->fulfillment_url);
GNUNET_CRYPTO_hash (pc->parse_pay.session_id,
strlen (pc->parse_pay.session_id),
&session_eh.h_session_id);
- GNUNET_CRYPTO_hash (pc->check_contract.contract_terms->fulfillment_url,
- strlen (pc->check_contract.contract_terms->
- fulfillment_url),
- &session_eh.h_fulfillment_url);
+ GNUNET_CRYPTO_hash (
+ pc->check_contract.contract_terms->pc->base->fulfillment_url,
+ strlen (pc->check_contract.contract_terms->pc->base->fulfillment_url),
+ &session_eh.h_fulfillment_url);
TALER_MERCHANTDB_event_notify (TMH_db,
&session_eh.header,
NULL,
@@ -2396,10 +2404,10 @@ phase_compute_money_pots (struct PayContext *pc)
TALER_amount_set_zero (pc->parse_pay.dc[0].cdd.amount.currency,
&assigned));
GNUNET_assert (NULL != contract);
- for (size_t i = 0; i<contract->products_len; i++)
+ for (size_t i = 0; i<contract->pc->products_len; i++)
{
const struct TALER_MERCHANT_ProductSold *product
- = &contract->products[i];
+ = &contract->pc->products[i];
const struct TALER_Amount *price = NULL;
/* find price in the right currency */
@@ -2460,14 +2468,14 @@ phase_compute_money_pots (struct PayContext *pc)
}
if ( (! TALER_amount_is_zero (&left)) &&
- (0 != contract->default_money_pot) )
+ (0 != contract->pc->base->default_money_pot) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Computing money pot %llu increment as %s\n",
- (unsigned long long) contract->default_money_pot,
+ (unsigned long long) contract->pc->base->default_money_pot,
TALER_amount2s (&left));
increment_pot (pc,
- contract->default_money_pot,
+ contract->pc->base->default_money_pot,
&left);
}
}
@@ -3124,7 +3132,7 @@ phase_execute_pay_transaction (struct PayContext *pc)
{
const struct TALER_MERCHANT_ContractChoice *choice =
- &pc->check_contract.contract_terms->details.v1
+ &pc->check_contract.contract_terms->pc->details.v1
.choices[pc->parse_wallet_data.choice_index];
for (size_t i = 0; i<pc->output_tokens_len; i++)
@@ -3192,10 +3200,11 @@ phase_execute_pay_transaction (struct PayContext *pc)
}
}
- TMH_notify_order_change (hc->instance,
- TMH_OSF_CLAIMED | TMH_OSF_PAID,
- pc->check_contract.contract_terms->timestamp,
- pc->check_contract.order_serial);
+ TMH_notify_order_change (
+ hc->instance,
+ TMH_OSF_CLAIMED | TMH_OSF_PAID,
+ pc->check_contract.contract_terms->pc->timestamp,
+ pc->check_contract.order_serial);
{
enum GNUNET_DB_QueryStatus qs;
json_t *jhook;
@@ -3531,11 +3540,11 @@ find_family (const struct PayContext *pc,
const char *slug)
{
for (unsigned int i = 0;
- i < pc->check_contract.contract_terms->details.v1.token_authorities_len;
+ i < pc->check_contract.contract_terms->pc->details.v1.token_authorities_len;
i++)
{
const struct TALER_MERCHANT_ContractTokenFamily *tfi
- = &pc->check_contract.contract_terms->details.v1.token_authorities[i];
+ = &pc->check_contract.contract_terms->pc->details.v1.token_authorities[i];
if (0 == strcmp (tfi->slug,
slug))
@@ -3613,8 +3622,8 @@ handle_output_token (struct PayContext *pc,
TMH_db,
pc->hc->instance->settings.id,
family->slug,
- pc->check_contract.contract_terms->timestamp,
- pc->check_contract.contract_terms->pay_deadline,
+ pc->check_contract.contract_terms->pc->timestamp,
+ pc->check_contract.contract_terms->pc->pay_deadline,
&details);
switch (qs)
{
@@ -3637,9 +3646,9 @@ handle_output_token (struct PayContext *pc,
"Token-family key for %s not found at [%llu,%llu]\n",
family->slug,
(unsigned long long)
- pc->check_contract.contract_terms->timestamp.abs_time.abs_value_us,
+ pc->check_contract.contract_terms->pc->timestamp.abs_time.abs_value_us,
(unsigned long long)
- pc->check_contract.contract_terms->pay_deadline.abs_time.abs_value_us
+ pc->check_contract.contract_terms->pc->pay_deadline.abs_time.abs_value_us
);
GNUNET_break (0);
pay_end (pc,
@@ -3837,20 +3846,20 @@ phase_validate_tokens (struct PayContext *pc)
/* We haven't seen a donau output yet. */
pc->validate_tokens.donau_output_index = -1;
- switch (pc->check_contract.contract_terms->version)
+ switch (pc->check_contract.contract_terms->pc->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
/* No tokens to validate */
pc->phase = PP_COMPUTE_MONEY_POTS;
pc->validate_tokens.max_fee
- = pc->check_contract.contract_terms->details.v0.max_fee;
+ = pc->check_contract.contract_terms->pc->details.v0.max_fee;
pc->validate_tokens.brutto
- = pc->check_contract.contract_terms->details.v0.brutto;
+ = pc->check_contract.contract_terms->pc->details.v0.brutto;
break;
case TALER_MERCHANT_CONTRACT_VERSION_1:
{
const struct TALER_MERCHANT_ContractChoice *selected
- = &pc->check_contract.contract_terms->details.v1.choices[
+ = &pc->check_contract.contract_terms->pc->details.v1.choices[
pc->parse_wallet_data.choice_index];
unsigned int output_off;
unsigned int cnt;
@@ -4175,8 +4184,8 @@ append_output_token_sig (void *cls,
0,
sizeof (out));
GNUNET_assert (TALER_MERCHANT_CONTRACT_VERSION_1 ==
- pc->check_contract.contract_terms->version);
- choice = &pc->check_contract.contract_terms->details.v1
+ pc->check_contract.contract_terms->pc->base->version);
+ choice = &pc->check_contract.contract_terms->pc->details.v1
.choices[pc->parse_wallet_data.choice_index];
output = &choice->outputs[pc->output_index_gen];
cnt = count_output_tokens (pc,
@@ -4374,15 +4383,16 @@ phase_check_contract (struct PayContext *pc)
TALER_MERCHANT_contract_free (pc->check_contract.contract_terms);
pc->check_contract.contract_terms = NULL;
}
- qs = TALER_MERCHANTDB_lookup_contract_terms2 (TMH_db,
- pc->hc->instance->settings.id,
- pc->order_id,
- &pc->check_contract.contract_terms_json,
- &pc->check_contract.order_serial,
- &paid,
- NULL,
- &pc->check_contract.pos_key,
- &pc->check_contract.pos_alg);
+ qs = TALER_MERCHANTDB_lookup_contract_terms2 (
+ TMH_db,
+ pc->hc->instance->settings.id,
+ pc->order_id,
+ &pc->check_contract.contract_terms_json,
+ &pc->check_contract.order_serial,
+ &paid,
+ NULL,
+ &pc->check_contract.pos_key,
+ &pc->check_contract.pos_alg);
if (0 > qs)
{
/* single, read-only SQL statements should never cause
@@ -4432,8 +4442,7 @@ phase_check_contract (struct PayContext *pc)
as later phases need it. */
pc->check_contract.contract_terms = TALER_MERCHANT_contract_parse (
- pc->check_contract.contract_terms_json,
- true);
+ pc->check_contract.contract_terms_json);
if (NULL == pc->check_contract.contract_terms)
{
@@ -4463,7 +4472,7 @@ phase_check_contract (struct PayContext *pc)
/* Check fundamentals */
{
- switch (pc->check_contract.contract_terms->version)
+ switch (pc->check_contract.contract_terms->pc->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
{
@@ -4498,14 +4507,15 @@ phase_check_contract (struct PayContext *pc)
return;
}
if (pc->parse_wallet_data.choice_index >=
- pc->check_contract.contract_terms->details.v1.choices_len)
+ pc->check_contract.contract_terms->pc->details.v1.choices_len)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Order `%s' has choices array with %u elements but "
- "request has 'choice_index' field with value %d\n",
- pc->order_id,
- pc->check_contract.contract_terms->details.v1.choices_len,
- pc->parse_wallet_data.choice_index);
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' has choices array with %u elements but "
+ "request has 'choice_index' field with value %d\n",
+ pc->order_id,
+ pc->check_contract.contract_terms->pc->details.v1.choices_len,
+ pc->parse_wallet_data.choice_index);
GNUNET_break (0);
pay_end (pc,
TALER_MHD_reply_with_error (
@@ -4530,11 +4540,10 @@ phase_check_contract (struct PayContext *pc)
}
}
- if (GNUNET_TIME_timestamp_cmp (pc->check_contract.contract_terms->
- wire_deadline,
- <,
- pc->check_contract.contract_terms->
- refund_deadline))
+ if (GNUNET_TIME_timestamp_cmp (
+ pc->check_contract.contract_terms->pc->wire_deadline,
+ <,
+ pc->check_contract.contract_terms->pc->refund_deadline))
{
/* This should already have been checked when creating the order! */
GNUNET_break (0);
@@ -4546,8 +4555,8 @@ phase_check_contract (struct PayContext *pc)
NULL));
return;
}
- if (GNUNET_TIME_absolute_is_past (pc->check_contract.contract_terms->
- pay_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_past (
+ pc->check_contract.contract_terms->pc->pay_deadline.abs_time))
{
/* too late */
pay_end (pc,
@@ -4564,8 +4573,10 @@ phase_check_contract (struct PayContext *pc)
struct TMH_WireMethod *wm;
wm = pc->hc->instance->wm_head;
- while (0 != GNUNET_memcmp (&pc->check_contract.contract_terms->h_wire,
- &wm->h_wire))
+ while (0 !=
+ GNUNET_memcmp (
+ &pc->check_contract.contract_terms->pc->h_wire,
+ &wm->h_wire))
wm = wm->next;
if (NULL == wm)
{
diff --git a/src/backend/taler-merchant-httpd_post-private-orders.c b/src/backend/taler-merchant-httpd_post-private-orders.c
@@ -318,169 +318,20 @@ struct OrderContext
{
/**
- * Our order ID.
+ * The main order data as provided by the client.
*/
- char *order_id;
+ struct TALER_MERCHANT_Order *order;
/**
- * Summary of the contract.
- */
- const char *summary;
-
- /**
- * Internationalized summary.
- */
- const json_t *summary_i18n;
-
- /**
- * URL that will show that the contract was successful
- * after it has been paid for.
- */
- const char *fulfillment_url;
-
- /**
- * Message shown to the customer after paying for the contract.
- * Either fulfillment_url or fulfillment_message must be specified.
- */
- const char *fulfillment_message;
-
- /**
- * Map from IETF BCP 47 language tags to localized fulfillment messages.
- */
- const json_t *fulfillment_message_i18n;
-
- /**
- * Length of the @e products array.
- */
- size_t products_len;
-
- /**
- * Array of products that are being sold.
- */
- struct TALER_MERCHANT_ProductSold *products;
-
- /**
- * URL where the same contract could be ordered again (if available).
- */
- const char *public_reorder_url;
-
- /**
- * Merchant base URL.
+ * Base URL of this merchant.
*/
char *merchant_base_url;
/**
- * Timestamp of the order.
- */
- struct GNUNET_TIME_Timestamp timestamp;
-
- /**
- * Deadline for refunds.
- */
- struct GNUNET_TIME_Timestamp refund_deadline;
-
- /**
- * Payment deadline.
- */
- struct GNUNET_TIME_Timestamp pay_deadline;
-
- /**
- * Wire transfer deadline.
- */
- struct GNUNET_TIME_Timestamp wire_deadline;
-
- /**
* Wire transfer round-up interval to apply.
*/
enum GNUNET_TIME_RounderInterval wire_deadline_rounder;
- /**
- * Delivery date.
- */
- struct GNUNET_TIME_Timestamp delivery_date;
-
- /**
- * Delivery location.
- */
- const json_t *delivery_location;
-
- /**
- * Specifies for how long the wallet should try to get an
- * automatic refund for the purchase.
- */
- struct GNUNET_TIME_Relative auto_refund;
-
- /**
- * Nonce generated by the wallet and echoed by the merchant
- * in this field when the proposal is generated.
- */
- const char *nonce;
-
- /**
- * Extra data that is only interpreted by the merchant frontend.
- */
- const json_t *extra;
-
- /**
- * Minimum age required by the order.
- */
- uint32_t minimum_age;
-
- /**
- * Money pot to increment for whatever order payment amount
- * is not yet assigned to a pot via the Product.
- */
- uint64_t order_default_money_pot;
-
- /**
- * Version of the contract terms.
- */
- enum TALER_MERCHANT_ContractVersion version;
-
- /**
- * Details present depending on @e version.
- */
- union
- {
- /**
- * Details only present for v0.
- */
- struct
- {
- /**
- * Gross amount value of the contract. Used to
- * compute @e max_stefan_fee.
- */
- struct TALER_Amount brutto;
-
- /**
- * Tip included by the customer (part of the total amount).
- */
- struct TALER_Amount tip;
-
- /**
- * True if @e tip was not provided.
- */
- bool no_tip;
-
- /**
- * Maximum fee as given by the client request.
- */
- struct TALER_Amount max_fee;
- } v0;
-
- /**
- * Details only present for v1.
- */
- struct
- {
- /**
- * Array of contract choices. Is null for v0 contracts.
- */
- const json_t *choices;
- } v1;
- } details;
-
} parse_order;
/**
@@ -956,14 +807,20 @@ clean_order (void *cls)
json_decref (oc->set_exchanges.exchange_rejections);
oc->set_exchanges.exchange_rejections = NULL;
}
- switch (oc->parse_order.version)
+ if (NULL != oc->parse_order.order)
{
- case TALER_MERCHANT_CONTRACT_VERSION_0:
- break;
- case TALER_MERCHANT_CONTRACT_VERSION_1:
- GNUNET_free (oc->set_max_fee.details.v1.max_fees);
- GNUNET_free (oc->set_exchanges.details.v1.max_stefan_fees);
- break;
+ switch (oc->parse_order.order->base->version)
+ {
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ GNUNET_free (oc->set_max_fee.details.v1.max_fees);
+ GNUNET_free (oc->set_exchanges.details.v1.max_stefan_fees);
+ break;
+ }
+ TALER_MERCHANT_order_free (oc->parse_order.order);
+ oc->parse_order.order = NULL;
+ GNUNET_free (oc->parse_order.merchant_base_url);
}
if (NULL != oc->merge_inventory.products)
{
@@ -977,12 +834,6 @@ clean_order (void *cls)
GNUNET_array_grow (oc->parse_choices.choices,
oc->parse_choices.choices_len,
0);
- for (size_t i = 0; i<oc->parse_order.products_len; i++)
- {
- TALER_MERCHANT_product_sold_free (&oc->parse_order.products[i]);
- }
- GNUNET_free (oc->parse_order.products);
- oc->parse_order.products_len = 0;
for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
{
struct TALER_MERCHANT_ContractTokenFamily *mctf
@@ -1028,8 +879,6 @@ clean_order (void *cls)
GNUNET_free (oc->parse_request.pos_key);
json_decref (oc->parse_request.order);
json_decref (oc->serialize_order.contract);
- GNUNET_free (oc->parse_order.order_id);
- GNUNET_free (oc->parse_order.merchant_base_url);
GNUNET_free (oc);
}
@@ -1126,7 +975,7 @@ execute_transaction (struct OrderContext *oc)
qs = TALER_MERCHANTDB_lookup_order (TMH_db,
oc->hc->instance->settings.id,
- oc->parse_order.order_id,
+ oc->parse_order.order->order_id,
&oc->execute_order.token,
&orig_post,
&contract_terms);
@@ -1160,10 +1009,10 @@ execute_transaction (struct OrderContext *oc)
/* Setup order */
qs = TALER_MERCHANTDB_insert_order (TMH_db,
oc->hc->instance->settings.id,
- oc->parse_order.order_id,
+ oc->parse_order.order->order_id,
oc->parse_request.session_id,
&oc->parse_request.h_post_data,
- oc->parse_order.pay_deadline,
+ oc->parse_order.order->pay_deadline,
&oc->parse_request.claim_token,
oc->serialize_order.contract, /* called 'contract terms' at database. */
oc->parse_request.pos_key,
@@ -1196,7 +1045,7 @@ execute_transaction (struct OrderContext *oc)
qs = TALER_MERCHANTDB_insert_order_lock (
TMH_db,
oc->hc->instance->settings.id,
- oc->parse_order.order_id,
+ oc->parse_order.order->order_id,
oc->parse_request.inventory_products[i].product_id,
oc->parse_request.inventory_products[i].quantity,
oc->parse_request.inventory_products[i].quantity_frac);
@@ -1217,11 +1066,12 @@ execute_transaction (struct OrderContext *oc)
/* Get the order serial and timestamp for the order we just created to
update long-poll clients. */
- qs = TALER_MERCHANTDB_lookup_order_summary (TMH_db,
- oc->hc->instance->settings.id,
- oc->parse_order.order_id,
- ×tamp,
- &order_serial);
+ qs = TALER_MERCHANTDB_lookup_order_summary (
+ TMH_db,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order->order_id,
+ ×tamp,
+ &order_serial);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
TALER_MERCHANTDB_rollback (TMH_db);
@@ -1233,7 +1083,7 @@ execute_transaction (struct OrderContext *oc)
jhook = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("order_id",
- oc->parse_order.order_id),
+ oc->parse_order.order->order_id),
GNUNET_JSON_pack_object_incref ("contract",
oc->serialize_order.contract),
GNUNET_JSON_pack_string ("instance_id",
@@ -1287,9 +1137,9 @@ yield_success_response (struct OrderContext *oc,
oc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_string ("order_id",
- oc->parse_order.order_id),
+ oc->parse_order.order->order_id),
GNUNET_JSON_pack_timestamp ("pay_deadline",
- oc->parse_order.pay_deadline),
+ oc->parse_order.order->pay_deadline),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_auto (
"token",
@@ -1315,7 +1165,7 @@ phase_execute_order (struct OrderContext *oc)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Executing database transaction to create order '%s' for instance '%s'\n",
- oc->parse_order.order_id,
+ oc->parse_order.order->order_id,
settings->id);
for (unsigned int i = 0; i<MAX_RETRIES; i++)
{
@@ -1344,7 +1194,7 @@ phase_execute_order (struct OrderContext *oc)
oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
- oc->parse_order.order_id);
+ oc->parse_order.order->order_id);
return;
}
/* Other hard transaction error (disk full, etc.) */
@@ -1372,7 +1222,7 @@ phase_execute_order (struct OrderContext *oc)
oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
- oc->parse_order.order_id);
+ oc->parse_order.order->order_id);
return;
}
@@ -1797,7 +1647,7 @@ add_input_token_family (struct OrderContext *oc,
const char *slug)
{
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
- struct GNUNET_TIME_Timestamp end = oc->parse_order.pay_deadline;
+ struct GNUNET_TIME_Timestamp end = oc->parse_order.order->pay_deadline;
enum GNUNET_DB_QueryStatus qs;
enum TALER_ErrorCode ec = TALER_EC_INVALID; /* make compiler happy */
unsigned int http_status = 0; /* make compiler happy */
@@ -1945,12 +1795,13 @@ add_output_token_family (struct OrderContext *oc,
valid_at,
key_index)) )
return GNUNET_OK;
- qs = TALER_MERCHANTDB_lookup_token_family_key (TMH_db,
- oc->hc->instance->settings.id,
- slug,
- valid_at,
- oc->parse_order.pay_deadline,
- &key_details);
+ qs = TALER_MERCHANTDB_lookup_token_family_key (
+ TMH_db,
+ oc->hc->instance->settings.id,
+ slug,
+ valid_at,
+ oc->parse_order.order->pay_deadline,
+ &key_details);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -2224,10 +2075,8 @@ output_contract_choices (struct OrderContext *oc)
GNUNET_assert (0 == json_array_append_new (
choices,
TALER_MERCHANT_json_from_contract_choice (
- &oc->parse_choices.choices[i],
- false)));
+ &oc->parse_choices.choices[i])));
}
-
return choices;
}
@@ -2291,83 +2140,61 @@ phase_serialize_order (struct OrderContext *oc)
}
oc->serialize_order.contract = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("version",
- oc->parse_order.version),
- GNUNET_JSON_pack_string ("summary",
- oc->parse_order.summary),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref (
- "summary_i18n",
- (json_t *) oc->parse_order.summary_i18n)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("public_reorder_url",
- oc->parse_order.public_reorder_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("fulfillment_message",
- oc->parse_order.fulfillment_message)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref (
- "fulfillment_message_i18n",
- (json_t *) oc->parse_order.fulfillment_message_i18n)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("fulfillment_url",
- oc->parse_order.fulfillment_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_uint64 ("minimum_age",
- oc->parse_order.minimum_age)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_uint64 ("default_money_pot",
- oc->parse_order.order_default_money_pot)),
- GNUNET_JSON_pack_array_incref ("products",
- oc->merge_inventory.products),
- GNUNET_JSON_pack_data_auto ("h_wire",
- &oc->select_wire_method.wm->h_wire),
- GNUNET_JSON_pack_string ("wire_method",
- oc->select_wire_method.wm->wire_method),
- GNUNET_JSON_pack_string ("order_id",
- oc->parse_order.order_id),
- GNUNET_JSON_pack_timestamp ("timestamp",
- oc->parse_order.timestamp),
- GNUNET_JSON_pack_timestamp ("pay_deadline",
- oc->parse_order.pay_deadline),
- GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
- oc->parse_order.wire_deadline),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("delivery_date",
- oc->parse_order.delivery_date)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref (
- "delivery_location",
- (json_t *) oc->parse_order.delivery_location)),
- GNUNET_JSON_pack_string ("merchant_base_url",
- oc->parse_order.merchant_base_url),
- GNUNET_JSON_pack_object_steal ("merchant",
- merchant),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- &oc->hc->instance->merchant_pub),
- GNUNET_JSON_pack_array_incref ("exchanges",
- oc->select_wire_method.exchanges),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("extra",
- (json_t *) oc->parse_order.extra))
- );
+ GNUNET_JSON_pack_string (
+ "order_id",
+ oc->parse_order.order->order_id),
+ GNUNET_JSON_pack_object_steal (
+ NULL,
+ TALER_MERCHANT_base_terms_serialize (oc->parse_order.order->base)),
+ GNUNET_JSON_pack_array_incref (
+ "products",
+ oc->merge_inventory.products),
+ GNUNET_JSON_pack_data_auto (
+ "h_wire",
+ &oc->select_wire_method.wm->h_wire),
+ GNUNET_JSON_pack_string (
+ "wire_method",
+ oc->select_wire_method.wm->wire_method),
+ GNUNET_JSON_pack_timestamp (
+ "timestamp",
+ oc->parse_order.order->timestamp),
+ GNUNET_JSON_pack_timestamp (
+ "pay_deadline",
+ oc->parse_order.order->pay_deadline),
+ GNUNET_JSON_pack_timestamp (
+ "wire_transfer_deadline",
+ oc->parse_order.order->wire_transfer_deadline),
+ GNUNET_JSON_pack_string (
+ "merchant_base_url",
+ oc->parse_order.merchant_base_url),
+ GNUNET_JSON_pack_object_steal (
+ "merchant",
+ merchant),
+ GNUNET_JSON_pack_data_auto (
+ "merchant_pub",
+ &oc->hc->instance->merchant_pub),
+ GNUNET_JSON_pack_array_incref (
+ "exchanges",
+ oc->select_wire_method.exchanges));
{
json_t *xtra;
- switch (oc->parse_order.version)
+ switch (oc->parse_order.order->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
xtra = GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("max_fee",
&oc->set_max_fee.details.v0.max_fee),
GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_amount ("tip",
- oc->parse_order.details.v0.no_tip
+ TALER_JSON_pack_amount (
+ "tip",
+ oc->parse_order.order->details.v0.no_tip
? NULL
- : &oc->parse_order.details.v0.tip)),
- TALER_JSON_pack_amount ("amount",
- &oc->parse_order.details.v0.brutto));
+ : &oc->parse_order.order->details.v0.tip)),
+ TALER_JSON_pack_amount (
+ "amount",
+ &oc->parse_order.order->details.v0.brutto));
break;
case TALER_MERCHANT_CONTRACT_VERSION_1:
{
@@ -2399,19 +2226,22 @@ phase_serialize_order (struct OrderContext *oc)
/* Pack does not work here, because it doesn't set zero-values for timestamps */
GNUNET_assert (0 ==
- json_object_set_new (oc->serialize_order.contract,
- "refund_deadline",
- GNUNET_JSON_from_timestamp (
- oc->parse_order.refund_deadline)));
+ json_object_set_new (
+ oc->serialize_order.contract,
+ "refund_deadline",
+ GNUNET_JSON_from_timestamp (
+ oc->parse_order.order->refund_deadline)));
/* auto_refund should only be set if it is not 0 */
- if (! GNUNET_TIME_relative_is_zero (oc->parse_order.auto_refund))
+ if (! GNUNET_TIME_relative_is_zero (
+ oc->parse_order.order->base->auto_refund))
{
/* Pack does not work here, because it sets zero-values for relative times */
GNUNET_assert (0 ==
- json_object_set_new (oc->serialize_order.contract,
- "auto_refund",
- GNUNET_JSON_from_time_rel (
- oc->parse_order.auto_refund)));
+ json_object_set_new (
+ oc->serialize_order.contract,
+ "auto_refund",
+ GNUNET_JSON_from_time_rel (
+ oc->parse_order.order->base->auto_refund)));
}
oc->phase++;
@@ -2472,12 +2302,12 @@ compute_fee (struct OrderContext *oc,
static void
phase_set_max_fee (struct OrderContext *oc)
{
- switch (oc->parse_order.version)
+ switch (oc->parse_order.order->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
compute_fee (oc,
- &oc->parse_order.details.v0.brutto,
- &oc->parse_order.details.v0.max_fee,
+ &oc->parse_order.order->details.v0.brutto,
+ &oc->parse_order.order->details.v0.max_fee,
&oc->set_exchanges.details.v0.max_stefan_fee,
&oc->set_max_fee.details.v0.max_fee);
break;
@@ -2525,11 +2355,11 @@ phase_select_wire_method (struct OrderContext *oc)
{
unsigned int num_choices = 0;
- switch (oc->parse_order.version)
+ switch (oc->parse_order.order->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
want_choices = 1;
- ea = &oc->parse_order.details.v0.brutto;
+ ea = &oc->parse_order.order->details.v0.brutto;
if (TALER_amount_is_zero (ea) ||
TALER_amount_set_test_above (&wmc->total_exchange_limits,
ea))
@@ -2714,11 +2544,11 @@ static void
update_stefan (struct OrderContext *oc,
const struct TALER_EXCHANGE_Keys *keys)
{
- switch (oc->parse_order.version)
+ switch (oc->parse_order.order->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
compute_stefan_fee (keys,
- &oc->parse_order.details.v0.brutto,
+ &oc->parse_order.order->details.v0.brutto,
&oc->set_exchanges.details.v0.max_stefan_fee);
break;
case TALER_MERCHANT_CONTRACT_VERSION_1:
@@ -3219,14 +3049,14 @@ static void
phase_add_payment_details (struct OrderContext *oc)
{
/* First, determine the maximum amounts that could be paid per currency */
- switch (oc->parse_order.version)
+ switch (oc->parse_order.order->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
GNUNET_array_append (oc->add_payment_details.max_choice_limits,
oc->add_payment_details.num_max_choice_limits,
- oc->parse_order.details.v0.brutto);
+ oc->parse_order.order->details.v0.brutto);
if (! TALER_amount_is_zero (
- &oc->parse_order.details.v0.brutto))
+ &oc->parse_order.order->details.v0.brutto))
{
oc->add_payment_details.need_exchange = true;
}
@@ -3242,7 +3072,8 @@ phase_add_payment_details (struct OrderContext *oc)
{
oc->add_payment_details.need_exchange = true;
}
- for (unsigned int j = 0; j<oc->add_payment_details.num_max_choice_limits;
+ for (unsigned int j = 0;
+ j<oc->add_payment_details.num_max_choice_limits;
j++)
{
struct TALER_Amount *mx = &oc->add_payment_details.max_choice_limits[j];
@@ -3344,25 +3175,26 @@ uint64_cmp (const void *a,
static void
phase_merge_inventory (struct OrderContext *oc)
{
- uint64_t pots[oc->parse_order.products_len + 1];
+ uint64_t pots[oc->parse_order.order->products_len + 1];
size_t pots_off = 0;
- if (0 != oc->parse_order.order_default_money_pot)
- pots[pots_off++] = oc->parse_order.order_default_money_pot;
+ if (0 != oc->parse_order.order->base->default_money_pot)
+ pots[pots_off++] = oc->parse_order.order->base->default_money_pot;
/**
* parse_request.inventory_products => instructions to add products to contract terms
* parse_order.products => contains products that are not from the backend-managed inventory.
*/
oc->merge_inventory.products = json_array ();
- for (size_t i = 0; i<oc->parse_order.products_len; i++)
+ for (size_t i = 0; i<oc->parse_order.order->products_len; i++)
{
GNUNET_assert (
0 ==
json_array_append_new (
oc->merge_inventory.products,
- TALER_MERCHANT_product_sold_serialize (&oc->parse_order.products[i])));
- if (0 != oc->parse_order.products[i].product_money_pot)
- pots[pots_off++] = oc->parse_order.products[i].product_money_pot;
+ TALER_MERCHANT_product_sold_serialize (
+ &oc->parse_order.order->products[i])));
+ if (0 != oc->parse_order.order->products[i].product_money_pot)
+ pots[pots_off++] = oc->parse_order.order->products[i].product_money_pot;
}
/* make sure pots array only has distinct elements */
@@ -3486,8 +3318,8 @@ phase_merge_inventory (struct OrderContext *oc)
return;
}
GNUNET_free (categories);
- oc->parse_order.minimum_age
- = GNUNET_MAX (oc->parse_order.minimum_age,
+ oc->parse_order.order->base->minimum_age
+ = GNUNET_MAX (oc->parse_order.order->base->minimum_age,
pd.minimum_age);
{
const char *eparam;
@@ -3538,7 +3370,7 @@ phase_merge_inventory (struct OrderContext *oc)
.prices_are_net = pd.price_is_net,
.image = pd.image,
.taxes = pd.taxes,
- .delivery_date = oc->parse_order.delivery_date,
+ .delivery_date = oc->parse_order.order->base->delivery_date,
.product_money_pot = pd.money_pot_id,
.unit = pd.unit,
@@ -3665,9 +3497,7 @@ add_donau_output (struct OrderContext *oc,
static void
phase_parse_choices (struct OrderContext *oc)
{
- const json_t *jchoices;
-
- switch (oc->parse_order.version)
+ switch (oc->parse_order.order->base->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
oc->phase++;
@@ -3679,245 +3509,157 @@ phase_parse_choices (struct OrderContext *oc)
GNUNET_assert (0);
}
- jchoices = oc->parse_order.details.v1.choices;
-
- if (! json_is_array (jchoices))
- GNUNET_assert (0);
- if (0 == json_array_size (jchoices))
- {
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "choices");
- return;
- }
+ /* Convert order choices to contract choices */
GNUNET_array_grow (oc->parse_choices.choices,
oc->parse_choices.choices_len,
- json_array_size (jchoices));
+ oc->parse_order.order->details.v1.choices_len);
for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
{
- struct TALER_MERCHANT_ContractChoice *choice
+ const struct TALER_MERCHANT_OrderChoice *ochoice
+ = &oc->parse_order.order->details.v1.choices[i];
+ struct TALER_MERCHANT_ContractChoice *cchoice
= &oc->parse_choices.choices[i];
- const char *error_name;
- unsigned int error_line;
- const json_t *jinputs;
- const json_t *joutputs;
- bool no_fee;
- 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 (
- TALER_JSON_spec_amount_any ("max_fee",
- &choice->max_fee),
- &no_fee),
- 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 (
- 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 ()
- };
- enum GNUNET_GenericReturnValue ret;
-
- ret = GNUNET_JSON_parse (json_array_get (jchoices,
- i),
- spec,
- &error_name,
- &error_line);
- if (GNUNET_OK != ret)
- {
- GNUNET_break_op (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Choice parsing failed: %s:%u\n",
- error_name,
- error_line);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "choice");
- return;
- }
- if ( (! no_fee) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&choice->amount,
- &choice->max_fee)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- "different currencies used for 'max_fee' and 'amount' currency");
- return;
- }
- if ( (! choice->no_tip) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&choice->amount,
- &choice->tip)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- "tip and amount");
- return;
- }
+ unsigned int off;
if (! TMH_test_exchange_configured_for_currency (
- choice->amount.currency))
+ ochoice->amount.currency))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
- choice->amount.currency);
+ ochoice->amount.currency);
return;
}
-
- if (NULL != jinputs)
+ cchoice->amount = ochoice->amount;
+ cchoice->tip = ochoice->tip;
+ cchoice->no_tip = ochoice->no_tip;
+ if (NULL != ochoice->description)
+ cchoice->description = GNUNET_strdup (ochoice->description);
+ if (NULL != ochoice->description_i18n)
+ cchoice->description_i18n = json_incref (ochoice->description_i18n);
+ cchoice->max_fee = ochoice->max_fee;
+
+ /* convert inputs */
+ GNUNET_array_grow (cchoice->inputs,
+ cchoice->inputs_len,
+ ochoice->inputs_len);
+ off = 0;
+ for (unsigned int j = 0; j < ochoice->inputs_len; j++)
{
- const json_t *jinput;
- size_t idx;
- json_array_foreach ((json_t *) jinputs, idx, jinput)
- {
- struct TALER_MERCHANT_ContractInput input = {
- .details.token.count = 1
- };
+ const struct TALER_MERCHANT_OrderInput *order_input
+ = &ochoice->inputs[j];
+ struct TALER_MERCHANT_ContractInput *contract_input
+ = &cchoice->inputs[off];
+ contract_input->type = order_input->type;
+ switch (order_input->type)
+ {
+ case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
+ /* Ignore inputs tokens with 'count' field set to 0 */
+ if (0 == order_input->details.token.count)
+ continue;
+ contract_input->details.token.count
+ = order_input->details.token.count;
+ contract_input->details.token.token_family_slug
+ = order_input->details.token.token_family_slug;
if (GNUNET_OK !=
- TALER_MERCHANT_parse_choice_input ((json_t *) jinput,
- &input,
- idx,
- true))
+ add_input_token_family (oc,
+ contract_input->details.token.token_family_slug))
{
GNUNET_break_op (0);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "input");
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
+ contract_input->details.token.token_family_slug);
return;
}
+ off++;
+ continue;
+ } /* switch input type */
+ GNUNET_assert (0);
+ } /* for all inputs */
+ GNUNET_array_grow (cchoice->inputs,
+ cchoice->inputs_len,
+ off);
+
+ /* convert outputs */
+ GNUNET_array_grow (cchoice->outputs,
+ cchoice->outputs_len,
+ ochoice->outputs_len);
+ off = 0;
+ for (unsigned int j = 0; j < ochoice->outputs_len; j++)
+ {
+ const struct TALER_MERCHANT_OrderOutput *order_output
+ = &ochoice->outputs[j];
+ struct TALER_MERCHANT_ContractOutput *contract_output
+ = &cchoice->outputs[off];
- switch (input.type)
+ switch (order_output->type)
+ {
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
+ if (! order_output->details.donation_receipt.no_amount)
{
- case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
- GNUNET_assert (0);
- break;
- case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
- /* Ignore inputs tokens with 'count' field set to 0 */
- if (0 == input.details.token.count)
- continue;
-
- if (GNUNET_OK !=
- add_input_token_family (oc,
- input.details.token.token_family_slug))
-
- {
- GNUNET_break_op (0);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
- input.details.token.token_family_slug);
- return;
- }
-
- GNUNET_array_append (choice->inputs,
- choice->inputs_len,
- input);
- continue;
+ contract_output->details.donation_receipt.amount
+ = ochoice->amount;
}
- GNUNET_assert (0);
- }
- }
-
- 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_choice_output ((json_t *) joutput,
- &output,
- idx,
- true))
+ else
{
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "output");
+ contract_output->details.donation_receipt.amount
+ = order_output->details.donation_receipt.amount;
+ }
+ if (! add_donau_output (oc,
+ contract_output))
+ {
+ GNUNET_break (0);
return;
}
+ off++;
+ continue;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
+ /* Ignore inputs tokens with 'count' field set to 0 */
+ if (0 == order_output->details.token.count)
+ continue;
+
+ contract_output->details.token.token_family_slug
+ = order_output->details.token.token_family_slug;
+ contract_output->details.token.count
+ = order_output->details.token.count;
+ if (0 == order_output->details.token.valid_at.abs_time.abs_value_us)
+ contract_output->details.token.valid_at
+ = GNUNET_TIME_timestamp_get ();
+ else
+ contract_output->details.token.valid_at
+ = order_output->details.token.valid_at;
+ if (GNUNET_OK !=
+ add_output_token_family (
+ oc,
+ contract_output->details.token.token_family_slug,
+ contract_output->details.token.valid_at,
+ &contract_output->details.token.key_index))
- switch (output.type)
{
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
- GNUNET_assert (0);
- break;
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
- output.details.donation_receipt.amount = choice->amount;
- if (! add_donau_output (oc,
- &output))
- {
- GNUNET_break (0);
- return;
- }
- GNUNET_array_append (choice->outputs,
- choice->outputs_len,
- output);
- continue;
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
- /* Ignore inputs tokens with 'count' field set to 0 */
- if (0 == output.details.token.count)
- continue;
-
- if (0 == output.details.token.valid_at.abs_time.abs_value_us)
- output.details.token.valid_at
- = GNUNET_TIME_timestamp_get ();
- if (GNUNET_OK !=
- add_output_token_family (oc,
- output.details.token.token_family_slug,
- output.details.token.valid_at,
- &output.details.token.key_index))
-
- {
- /* note: reply_with_error() was already called */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Could not handle output token family `%s'\n",
- output.details.token.token_family_slug);
- return;
- }
-
- GNUNET_array_append (choice->outputs,
- choice->outputs_len,
- output);
- continue;
+ /* note: reply_with_error() was already called */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Could not handle output token family `%s'\n",
+ contract_output->details.token.token_family_slug);
+ return;
}
- GNUNET_assert (0);
- }
- }
- }
+ off++;
+ continue;
+ } /* end switch */
+ GNUNET_assert (0);
+ } /* for outputs */
+ GNUNET_array_grow (cchoice->outputs,
+ cchoice->outputs_len,
+ off);
+ } /* for all choices */
oc->phase++;
}
@@ -3936,249 +3678,40 @@ phase_parse_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
- const char *merchant_base_url = NULL;
- uint64_t version = 0;
- const json_t *jmerchant = NULL;
- const json_t *products = NULL;
- const char *order_id = NULL;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("version",
- &version),
- NULL),
- GNUNET_JSON_spec_string ("summary",
- &oc->parse_order.summary),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("products",
- &products),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("summary_i18n",
- &oc->parse_order.summary_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_slug ("order_id",
- &order_id),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_message",
- &oc->parse_order.fulfillment_message),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("fulfillment_message_i18n",
- &oc->parse_order.fulfillment_message_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_url",
- &oc->parse_order.fulfillment_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("public_reorder_url",
- &oc->parse_order.public_reorder_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_web_url ("merchant_base_url",
- &merchant_base_url),
- NULL),
- /* For sanity check, this field must NOT be present */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("merchant",
- &jmerchant),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("timestamp",
- &oc->parse_order.timestamp),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &oc->parse_order.refund_deadline),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &oc->parse_order.pay_deadline),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &oc->parse_order.wire_deadline),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("delivery_location",
- &oc->parse_order.delivery_location),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("delivery_date",
- &oc->parse_order.delivery_date),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("minimum_age",
- &oc->parse_order.minimum_age),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_relative_time ("auto_refund",
- &oc->parse_order.auto_refund),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("extra",
- &oc->parse_order.extra),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("order_default_money_pot",
- &oc->parse_order.order_default_money_pot),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue ret;
bool computed_refund_deadline = false;
- oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
- oc->parse_order.wire_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
- ret = TALER_MHD_parse_json_data (oc->connection,
- oc->parse_request.order,
- spec);
- if (GNUNET_OK != ret)
+ oc->parse_order.order
+ = TALER_MERCHANT_order_parse (
+ oc->parse_request.order);
+ if (NULL == oc->parse_order.order)
{
GNUNET_break_op (0);
- finalize_order2 (oc,
- ret);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order");
return;
}
- if ( (NULL != products) &&
- (0 != (oc->parse_order.products_len = json_array_size (products))) )
- {
- size_t i;
- json_t *p;
- oc->parse_order.products
- = GNUNET_new_array (oc->parse_order.products_len,
- struct TALER_MERCHANT_ProductSold);
- json_array_foreach (products, i, p)
- {
- if (GNUNET_OK !=
- TALER_MERCHANT_parse_product_sold (p,
- &oc->parse_order.products[i]))
- {
- GNUNET_break_op (0);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order.products");
- return;
- }
- }
- }
- switch (version)
+ switch (oc->parse_order.order->base->version)
{
- case 0:
- {
- bool no_fee;
- const json_t *choices = NULL;
- struct GNUNET_JSON_Specification specv0[] = {
- TALER_JSON_spec_amount_any (
- "amount",
- &oc->parse_order.details.v0.brutto),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any (
- "tip",
- &oc->parse_order.details.v0.tip),
- &oc->parse_order.details.v0.no_tip),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any (
- "max_fee",
- &oc->parse_order.details.v0.max_fee),
- &no_fee),
- /* for sanity check, must be *absent*! */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("choices",
- &choices),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- ret = TALER_MHD_parse_json_data (oc->connection,
- oc->parse_request.order,
- specv0);
- if (GNUNET_OK != ret)
- {
- GNUNET_break_op (0);
- finalize_order2 (oc,
- ret);
- return;
- }
- if ( (! no_fee) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&oc->parse_order.details.v0.brutto,
- &oc->parse_order.details.v0.max_fee)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- "different currencies used for 'max_fee' and 'amount' currency");
- return;
- }
- if ( (! oc->parse_order.details.v0.no_tip) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&oc->parse_order.details.v0.brutto,
- &oc->parse_order.details.v0.tip)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- "tip and amount");
- return;
- }
- if (! TMH_test_exchange_configured_for_currency (
- oc->parse_order.details.v0.brutto.currency))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- reply_with_error (oc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
- oc->parse_order.details.v0.brutto.currency);
- return;
- }
- if (NULL != choices)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
- "choices array must be null for v0 contracts");
- return;
- }
- oc->parse_order.version = TALER_MERCHANT_CONTRACT_VERSION_0;
- break;
- }
- case 1:
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ if (! TMH_test_exchange_configured_for_currency (
+ oc->parse_order.order->details.v0.brutto.currency))
{
- struct GNUNET_JSON_Specification specv1[] = {
- GNUNET_JSON_spec_array_const (
- "choices",
- &oc->parse_order.details.v1.choices),
- GNUNET_JSON_spec_end ()
- };
-
- ret = TALER_MHD_parse_json_data (oc->connection,
- oc->parse_request.order,
- specv1);
- if (GNUNET_OK != ret)
- {
- GNUNET_break_op (0);
- finalize_order2 (oc,
- ret);
- return;
- }
- oc->parse_order.version = TALER_MERCHANT_CONTRACT_VERSION_1;
- break;
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
+ oc->parse_order.order->details.v0.brutto.currency);
+ return;
}
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ break;
default:
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_VERSION_MALFORMED,
@@ -4187,11 +3720,7 @@ phase_parse_order (struct OrderContext *oc)
}
/* Add order_id if it doesn't exist. */
- if (NULL != order_id)
- {
- oc->parse_order.order_id = GNUNET_strdup (order_id);
- }
- else
+ if (NULL == oc->parse_order.order->order_id)
{
char buf[256];
time_t timer;
@@ -4204,7 +3733,6 @@ phase_parse_order (struct OrderContext *oc)
tm_info = localtime (&timer);
if (NULL == tm_info)
{
- GNUNET_JSON_parse_free (spec);
reply_with_error (
oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
@@ -4231,17 +3759,15 @@ phase_parse_order (struct OrderContext *oc)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Assigning order ID `%s' server-side\n",
buf);
-
- oc->parse_order.order_id = GNUNET_strdup (buf);
- GNUNET_assert (NULL != oc->parse_order.order_id);
+ oc->parse_order.order->order_id = GNUNET_strdup (buf);
}
/* Patch fulfillment URL with order_id (implements #6467). */
- if (NULL != oc->parse_order.fulfillment_url)
+ if (NULL != oc->parse_order.order->base->fulfillment_url)
{
const char *pos;
- pos = strstr (oc->parse_order.fulfillment_url,
+ pos = strstr (oc->parse_order.order->base->fulfillment_url,
"${ORDER_ID}");
if (NULL != pos)
{
@@ -4260,32 +3786,36 @@ phase_parse_order (struct OrderContext *oc)
return;
}
- GNUNET_asprintf (&nurl,
- "%.*s%s%s",
- /* first output URL until ${ORDER_ID} */
- (int) (pos - oc->parse_order.fulfillment_url),
- oc->parse_order.fulfillment_url,
- /* replace ${ORDER_ID} with the right order_id */
- oc->parse_order.order_id,
- /* append rest of original URL */
- pos + strlen ("${ORDER_ID}"));
-
- oc->parse_order.fulfillment_url = GNUNET_strdup (nurl);
-
+ GNUNET_asprintf (
+ &nurl,
+ "%.*s%s%s",
+ /* first output URL until ${ORDER_ID} */
+ (int) (pos - oc->parse_order.order->base->fulfillment_url),
+ oc->parse_order.order->base->fulfillment_url,
+ /* replace ${ORDER_ID} with the right order_id */
+ oc->parse_order.order->order_id,
+ /* append rest of original URL */
+ pos + strlen ("${ORDER_ID}"));
+ oc->parse_order.order->base->fulfillment_url = GNUNET_strdup (nurl);
GNUNET_free (nurl);
}
}
- if ( (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time)) ||
- (GNUNET_TIME_absolute_is_never (oc->parse_order.pay_deadline.abs_time)) )
+ if ( (GNUNET_TIME_absolute_is_zero (
+ oc->parse_order.order->pay_deadline.abs_time)) ||
+ (GNUNET_TIME_absolute_is_never (
+ oc->parse_order.order->pay_deadline.abs_time)) )
{
- oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp (
- settings->default_pay_delay);
+ oc->parse_order.order->pay_deadline
+ = GNUNET_TIME_relative_to_timestamp (
+ settings->default_pay_delay);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Pay deadline was zero (or never), setting to %s\n",
- GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline));
+ GNUNET_TIME_timestamp2s (
+ oc->parse_order.order->pay_deadline));
}
- else if (GNUNET_TIME_absolute_is_past (oc->parse_order.pay_deadline.abs_time))
+ else if (GNUNET_TIME_absolute_is_past (
+ oc->parse_order.order->pay_deadline.abs_time))
{
GNUNET_break_op (0);
reply_with_error (
@@ -4297,7 +3827,8 @@ phase_parse_order (struct OrderContext *oc)
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Pay deadline is %s\n",
- GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline));
+ GNUNET_TIME_timestamp2s (
+ oc->parse_order.order->pay_deadline));
/* Check soundness of refund deadline, and that a timestamp
* is actually present. */
@@ -4305,35 +3836,38 @@ phase_parse_order (struct OrderContext *oc)
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
/* Add timestamp if it doesn't exist (or is zero) */
- if (GNUNET_TIME_absolute_is_zero (oc->parse_order.timestamp.abs_time))
+ if (GNUNET_TIME_absolute_is_zero (
+ oc->parse_order.order->timestamp.abs_time))
{
- oc->parse_order.timestamp = now;
+ oc->parse_order.order->timestamp = now;
}
/* If no refund_deadline given, set one based on refund_delay. */
if (GNUNET_TIME_absolute_is_never (
- oc->parse_order.refund_deadline.abs_time))
+ oc->parse_order.order->refund_deadline.abs_time))
{
- if (GNUNET_TIME_relative_is_zero (oc->parse_request.refund_delay))
+ if (GNUNET_TIME_relative_is_zero (
+ oc->parse_request.refund_delay))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Refund delay is zero, no refunds are possible for this order\n");
- oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
+ oc->parse_order.order->refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
}
else
{
computed_refund_deadline = true;
- oc->parse_order.refund_deadline
+ oc->parse_order.order->refund_deadline
= GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (oc->parse_order.pay_deadline.abs_time,
- oc->parse_request.refund_delay));
+ GNUNET_TIME_absolute_add (
+ oc->parse_order.order->pay_deadline.abs_time,
+ oc->parse_request.refund_delay));
}
}
if ( (! GNUNET_TIME_absolute_is_zero (
- oc->parse_order.delivery_date.abs_time)) &&
+ oc->parse_order.order->base->delivery_date.abs_time)) &&
(GNUNET_TIME_absolute_is_past (
- oc->parse_order.delivery_date.abs_time)) )
+ oc->parse_order.order->base->delivery_date.abs_time)) )
{
GNUNET_break_op (0);
reply_with_error (
@@ -4346,9 +3880,9 @@ phase_parse_order (struct OrderContext *oc)
}
if ( (! GNUNET_TIME_absolute_is_zero (
- oc->parse_order.refund_deadline.abs_time)) &&
+ oc->parse_order.order->refund_deadline.abs_time)) &&
(GNUNET_TIME_absolute_is_past (
- oc->parse_order.refund_deadline.abs_time)) )
+ oc->parse_order.order->refund_deadline.abs_time)) )
{
GNUNET_break_op (0);
reply_with_error (
@@ -4359,14 +3893,15 @@ phase_parse_order (struct OrderContext *oc)
return;
}
- if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_never (
+ oc->parse_order.order->wire_transfer_deadline.abs_time))
{
struct GNUNET_TIME_Absolute start;
start = GNUNET_TIME_absolute_max (
- oc->parse_order.refund_deadline.abs_time,
- oc->parse_order.pay_deadline.abs_time);
- oc->parse_order.wire_deadline
+ oc->parse_order.order->refund_deadline.abs_time,
+ oc->parse_order.order->pay_deadline.abs_time);
+ oc->parse_order.order->wire_transfer_deadline
= GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_round_up (
GNUNET_TIME_absolute_add (
@@ -4374,7 +3909,7 @@ phase_parse_order (struct OrderContext *oc)
settings->default_wire_transfer_delay),
settings->default_wire_transfer_rounding_interval));
if (GNUNET_TIME_absolute_is_never (
- oc->parse_order.wire_deadline.abs_time))
+ oc->parse_order.order->wire_transfer_deadline.abs_time))
{
GNUNET_break_op (0);
reply_with_error (
@@ -4390,13 +3925,15 @@ phase_parse_order (struct OrderContext *oc)
/* if we computed the refund_deadline from default settings
and did have a configured wire_deadline, make sure that
the refund_deadline is at or below the wire_deadline. */
- oc->parse_order.refund_deadline
- = GNUNET_TIME_timestamp_min (oc->parse_order.refund_deadline,
- oc->parse_order.wire_deadline);
+ oc->parse_order.order->refund_deadline
+ = GNUNET_TIME_timestamp_min (
+ oc->parse_order.order->refund_deadline,
+ oc->parse_order.order->wire_transfer_deadline);
}
- if (GNUNET_TIME_timestamp_cmp (oc->parse_order.wire_deadline,
- <,
- oc->parse_order.refund_deadline))
+ if (GNUNET_TIME_timestamp_cmp (
+ oc->parse_order.order->wire_transfer_deadline,
+ <,
+ oc->parse_order.order->refund_deadline))
{
GNUNET_break_op (0);
reply_with_error (
@@ -4407,23 +3944,6 @@ phase_parse_order (struct OrderContext *oc)
return;
}
- if (NULL != merchant_base_url)
- {
- if (('\0' == *merchant_base_url) ||
- ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
- {
- GNUNET_break_op (0);
- reply_with_error (
- oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
- "merchant_base_url is not valid");
- return;
- }
- oc->parse_order.merchant_base_url
- = GNUNET_strdup (merchant_base_url);
- }
- else
{
char *url;
@@ -4442,20 +3962,9 @@ phase_parse_order (struct OrderContext *oc)
oc->parse_order.merchant_base_url = url;
}
- /* Merchant information must not already be present */
- if (NULL != jmerchant)
- {
- GNUNET_break_op (0);
- reply_with_error (
- oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
- "'merchant' field already set, but must be provided by backend");
- return;
- }
-
- if ( (NULL != oc->parse_order.delivery_location) &&
- (! TMH_location_object_valid (oc->parse_order.delivery_location)) )
+ // FIXME: move to util during parsing!
+ if ( (NULL != oc->parse_order.order->base->delivery_location) &&
+ (! TMH_location_object_valid (oc->parse_order.order->base->delivery_location)) )
{
GNUNET_break_op (0);
reply_with_error (oc,
diff --git a/src/backend/taler-merchant-httpd_post-templates-TEMPLATE_ID.c b/src/backend/taler-merchant-httpd_post-templates-TEMPLATE_ID.c
@@ -1390,7 +1390,7 @@ handle_phase_compute_price (struct UseContext *uc)
for (size_t i = 0; i < tcp->choices_len; i++)
{
/* Make deep copy, we're going to MODIFY it! */
- struct TALER_MERCHANT_ContractChoice choice
+ struct TALER_MERCHANT_OrderChoice choice
= tcp->choices[i];
choice.no_tip = uc->parse_request.no_tip;
@@ -1417,8 +1417,7 @@ handle_phase_compute_price (struct UseContext *uc)
GNUNET_assert (0 ==
json_array_append_new (
choices,
- TALER_MERCHANT_json_from_contract_choice (&choice,
- true)));
+ TALER_MERCHANT_json_from_order_choice (&choice)));
}
if (0 == json_array_size (choices))
{
diff --git a/src/include/taler/taler_merchant_util.h b/src/include/taler/taler_merchant_util.h
@@ -103,6 +103,7 @@ TALER_MERCHANT_vk_process_quantity_inputs (
uint32_t *frac_out,
const char **error_param);
+
/**
* Format a fixed-decimal pair into canonical string representation.
* Recognises INT64_MAX / INT32_MAX as the "-1" sentinel used for
@@ -228,748 +229,962 @@ enum TALER_MERCHANT_MFA_CriticalOperation
*/
TALER_MERCHANT_MFA_CO_AUTH_TOKEN_CREATION
-
};
+
/**
- * Template type discriminator.
+ * Possible versions of the contract terms.
*/
-enum TALER_MERCHANT_TemplateType
+enum TALER_MERCHANT_ContractVersion
{
- TALER_MERCHANT_TEMPLATE_TYPE_INVALID = 0,
- TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER,
- TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART,
- TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA
+
+ /**
+ * Version 0
+ */
+ TALER_MERCHANT_CONTRACT_VERSION_0 = 0,
+
+ /**
+ * Version 1
+ */
+ TALER_MERCHANT_CONTRACT_VERSION_1 = 1
};
/**
- * Determine template type from string.
- *
- * @param template_type string value (NULL means fixed-order)
- * @return template type (defaults to fixed order)
+ * Possible token kinds.
*/
-enum TALER_MERCHANT_TemplateType
-TALER_MERCHANT_template_type_from_string (const char *template_type);
+enum TALER_MERCHANT_ContractTokenKind
+{
+ /**
+ * Token kind invalid
+ */
+ TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID = 0,
+
+ /**
+ * Subscription token kind
+ */
+ TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION = 1,
+
+ /**
+ * Discount token kind
+ */
+ TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT = 2,
+};
-/**
- * Convert template type to its string representation.
- *
- * @param template_type template type to convert
- * @return string name or NULL for invalid types
- */
-const char *
-TALER_MERCHANT_template_type_to_string (
- enum TALER_MERCHANT_TemplateType template_type);
/**
- * Determine template type from a template contract.
+ * Get enum value from contract token type string.
*
- * @param template_contract contract JSON
- * @return template type (defaults to fixed order)
+ * @param str contract token type string
+ * @return enum value of token type
*/
-enum TALER_MERCHANT_TemplateType
-TALER_MERCHANT_template_type_from_contract (const json_t *template_contract);
+enum TALER_MERCHANT_ContractTokenKind
+TALER_MERCHANT_contract_token_kind_from_string (const char *str);
+
/**
- * Template contract fields for inventory templates.
+ * Possible input types for the contract terms.
*/
-struct TALER_MERCHANT_TemplateContractInventory
+enum TALER_MERCHANT_ContractInputType
{
- /**
- * Selected categories from the template contract.
- */
- const json_t *selected_categories;
/**
- * Selected products from the template contract.
+ * Input type invalid
*/
- const json_t *selected_products;
+ TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID = 0,
+#if FUTURE
/**
- * Whether all products are selectable.
+ * Input type coin
*/
- bool selected_all;
-
+ TALER_MERCHANT_CONTRACT_INPUT_TYPE_COIN = 1,
+#endif
/**
- * Template requires exactly one selection.
+ * Input type token
*/
- bool choose_one;
-
+ TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN = 2
};
/**
- * Template contract fields for paivana templates.
+ * Contract input (part of the v1 contract terms).
*/
-struct TALER_MERCHANT_TemplateContractPaivana
+struct TALER_MERCHANT_ContractInput
{
/**
- * Paivana website regular expression.
- * NULL to allow any site.
- */
- const char *website_regex;
+ * Type of the input.
+ */
+ enum TALER_MERCHANT_ContractInputType type;
- /**
- * Array of possible specific contracts the wallet/customer may choose
- * from by selecting the respective index when signing the deposit
- * confirmation.
- */
- struct TALER_MERCHANT_ContractChoice *choices;
+ union
+ {
+#if FUTURE
+ /**
+ * Coin-based input (ration). (Future work, only here for reference)
+ */
+ struct
+ {
+ /**
+ * Price to be paid.
+ */
+ struct TALER_Amount price;
- /**
- * Length of the @e choices array.
- */
- unsigned int choices_len;
+ /**
+ * Base URL of the ration authority.
+ */
+ const char *ration_authority_url;
+ } coin;
+#endif
+
+ /**
+ * Token-based input.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be used.
+ */
+ const char *token_family_slug;
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
};
+
/**
- * Parsed template contract.
+ * Order input (part of the v1 orders).
*/
-struct TALER_MERCHANT_TemplateContract
+struct TALER_MERCHANT_OrderInput
{
/**
- * Template type.
- */
- enum TALER_MERCHANT_TemplateType type;
+ * Type of the input.
+ */
+ enum TALER_MERCHANT_ContractInputType type;
- /**
- * Summary from the template contract.
- */
- const char *summary;
+ union
+ {
+#if FUTURE
+ /**
+ * Coin-based input (ration). (Future work, only here for reference)
+ */
+ struct
+ {
+ /**
+ * Price to be paid.
+ */
+ struct TALER_Amount price;
- /**
- * Currency from the template contract.
- */
- const char *currency;
+ /**
+ * Base URL of the ration authority.
+ */
+ const char *ration_authority_url;
+ } coin;
+#endif
- /**
- * Amount from the template contract.
- */
- struct TALER_Amount amount;
+ /**
+ * Token-based input.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be used.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
+};
+
+
+/**
+ * Possible output types for the contract terms.
+ */
+enum TALER_MERCHANT_ContractOutputType
+{
/**
- * True if @e amount was not provided.
+ * Invalid output type
*/
- bool no_amount;
+ TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID = 0,
/**
- * Template allows tips.
+ * Output type token
*/
- bool request_tip;
+ TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN = 1,
/**
- * Minimum age required by the template.
+ * Output type donation-receipt
*/
- uint32_t minimum_age;
-
+ TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT = 2,
+#if FUTURE
/**
- * How long does the customer have to pay for the order.
- * 0 if not specified (use instance default).
+ * Output type coin
*/
- struct GNUNET_TIME_Relative pay_duration;
+ TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN = 3
+#endif
+};
+
+/**
+ * Contract output (part of the v1 contract terms).
+ */
+struct TALER_MERCHANT_ContractOutput
+{
/**
- * How long does the user have at most to access/pickup the (digital)
- * goods or service they bought. Optional, defaults to FOREVER.
+ * Type of the output.
*/
- struct GNUNET_TIME_Relative max_pickup_duration;
+ enum TALER_MERCHANT_ContractOutputType type;
union
{
-
- /**
- * Parsed fields for inventory templates.
- */
- struct TALER_MERCHANT_TemplateContractInventory inventory;
-
+#if FUTURE
/**
- * Parsed fields for paivana templates.
+ * Coin-based output.
*/
- struct TALER_MERCHANT_TemplateContractPaivana paivana;
-
- } details;
-
-};
-
-/**
- * Parse template contract JSON into @a out.
- *
- * @param template_contract JSON object containing the template contract
- * @param[out] out parsed template contract
- * @param[out] error_name pointer to the name of the failed field, or NULL
- * @return #GNUNET_SYSERR if @a template_contract is malformed; #GNUNET_OK otherwise
- */
-enum GNUNET_GenericReturnValue
-TALER_MERCHANT_template_contract_parse (
- const json_t *template_contract,
- struct TALER_MERCHANT_TemplateContract *out,
- const char **error_name);
-
-
-/**
- * Release memory from template contract @a tc.
- * Does not free @a tc itself.
- *
- * @param[in] tc contract to free
- */
-void
-TALER_MERCHANT_template_contract_free (
- struct TALER_MERCHANT_TemplateContract *tc);
+ struct
+ {
+ /**
+ * Coins that will be yielded. This excludes any applicable withdraw fees.
+ */
+ struct TALER_Amount brutto_yield;
+ /**
+ * Base URL of the exchange that will issue the coins.
+ *
+ * NOTE: Once implemented, check if we need to allocate this here or if
+ * we again reference the JSON as we do in other places.
+ */
+ char *exchange_url;
-/**
- * Check if @a template_contract is valid.
- *
- * @param template_contract template contract to validate
- * @return true if @a template_contract is valid
- */
-bool
-TALER_MERCHANT_template_contract_valid (const json_t *template_contract);
+ } coin;
+#endif
+ /**
+ * DONAU-receipt output.
+ */
+ struct
+ {
+ /**
+ * Amount of the donation.
+ */
+ struct TALER_Amount amount;
+ /**
+ * Base URLs of the donation authorities that will issue the tax receipt.
+ */
+ char **donau_urls;
-/**
- * Convert critical operation enumeration value to string
- * suitable for human readers.
- *
- * @param co input to convert
- * @return operation value as string
- */
-const char *
-TALER_MERCHANT_MFA_co2s (
- enum TALER_MERCHANT_MFA_CriticalOperation co);
+ /**
+ * Length of the @e donau_urls array.
+ */
+ unsigned int donau_urls_len;
+ } donation_receipt;
-/**
- * Convert critical operation enumeration value to string.
- *
- * @param co input to convert
- * @return operation value as string
- */
-const char *
-TALER_MERCHANT_MFA_co_to_string (
- enum TALER_MERCHANT_MFA_CriticalOperation co);
+ /**
+ * Token-based output.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be issued.
+ * Note: this is a pointer into the JSON of the
+ * respective contract/request and not owned here.
+ */
+ const char *token_family_slug;
+ /**
+ * Index of the public key in the @a token_family_slug's token family
+ * ``keys`` array that this output token will have.
+ */
+ unsigned int key_index;
-/**
- * Convert string to critical operation enumeration value.
- *
- * @param str input to convert
- * @return #TALER_MERCHANT_MFA_CO_NONE on failure
- */
-enum TALER_MERCHANT_MFA_CriticalOperation
-TALER_MERCHANT_MFA_co_from_string (const char *str);
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ /**
+ * Determines when the output token should be valid.
+ * Optional, set to zero for not specified (then we
+ * use the current time).
+ */
+ struct GNUNET_TIME_Timestamp valid_at;
-/**
- * Convert MFA channel enumeration value to string.
- *
- * @param ch input to convert
- * @return operation value as string
- */
-const char *
-TALER_MERCHANT_MFA_channel_to_string (
- enum TALER_MERCHANT_MFA_Channel ch);
+ } token;
+ } details;
-/**
- * Convert string to MFA channel enumeration value.
- *
- * @param str input to convert
- * @return #TALER_MERCHANT_MFA_CHANNEL_NONE on failure
- */
-enum TALER_MERCHANT_MFA_Channel
-TALER_MERCHANT_MFA_channel_from_string (const char *str);
+};
/**
- * @brief Salted hash of a body for a request that required MFA.
+ * Order output (part of the v1 orders).
*/
-struct TALER_MERCHANT_MFA_BodyHash
+struct TALER_MERCHANT_OrderOutput
{
/**
- * Hash of the body and salt.
+ * Type of the output.
*/
- struct GNUNET_ShortHashCode hash;
+ enum TALER_MERCHANT_ContractOutputType type;
+
+ union
+ {
+#if FUTURE
+ /**
+ * Coin-based output.
+ */
+ struct
+ {
+ /**
+ * Coins that will be yielded. This excludes any applicable withdraw fees.
+ */
+ struct TALER_Amount brutto_yield;
+
+ /**
+ * Base URL of the exchange that will issue the coins.
+ *
+ * NOTE: Once implemented, check if we need to allocate this here or if
+ * we again reference the JSON as we do in other places.
+ */
+ char *exchange_url;
+
+ } coin;
+#endif
+ /**
+ * DONAU-receipt output.
+ */
+ struct
+ {
+ /**
+ * Amount of the donation. (optional)
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * True if @e amount is NOT set.
+ */
+ bool no_amount;
+
+ } donation_receipt;
+
+ /**
+ * Token-based output.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be issued.
+ * Note: this is a pointer into the JSON of the
+ * respective contract/request and not owned here.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+
+ /**
+ * Determines when the output token should be valid.
+ * Optional, set to zero for not specified (then we
+ * use the current time).
+ */
+ struct GNUNET_TIME_Timestamp valid_at;
+
+ } token;
+
+ } details;
+
};
/**
- * @brief Salt used when computing a `struct TALER_MERCHANT_MFA_BodyHash`
+ * Contract choice (part of the v1 contract terms).
*/
-struct TALER_MERCHANT_MFA_BodySalt
+struct TALER_MERCHANT_ContractChoice
{
+
/**
- * Salt.
+ * Amount to be paid for this choice.
*/
- uint64_t salt[128 / 64];
-};
+ struct TALER_Amount amount;
+ /**
+ * Tip included by the customer (part of the total amount).
+ */
+ struct TALER_Amount tip;
-/**
- * @brief Token used to authorize report generation.
- */
-struct TALER_MERCHANT_ReportToken
-{
/**
- * Salt.
+ * True if @e tip was not provided.
*/
- uint64_t salt[256 / 64];
-};
+ bool no_tip;
+ /**
+ * Human readable description of the semantics of the choice within the
+ * contract to be shown to the user at payment.
+ */
+ char *description;
-/**
- * Hash the given request @a body with the given @a salt to
- * produce @a h_body for MFA checks.
- *
- * @param body HTTP request body, NULL if body was empty
- * @param salt salt to use
- * @param h_body resulting hash
- */
-void
-TALER_MERCHANT_mfa_body_hash (
- const json_t *body,
- const struct TALER_MERCHANT_MFA_BodySalt *salt,
- struct TALER_MERCHANT_MFA_BodyHash *h_body);
+ /**
+ * Map from IETF BCP 47 language tags to localized description.
+ */
+ json_t *description_i18n;
+ /**
+ * Maximum fee the merchant is willing to pay for this choice.
+ * Set to an invalid amount to use instance defaults (zero or STEFAN).
+ */
+ struct TALER_Amount max_fee;
-/**
- * Possible versions of the contract terms.
- */
-enum TALER_MERCHANT_ContractVersion
-{
+ /**
+ * List of inputs the wallet must provision (all of them) to satisfy the
+ * conditions for the contract.
+ */
+ struct TALER_MERCHANT_ContractInput *inputs;
/**
- * Version 0
+ * Length of the @e inputs array.
*/
- TALER_MERCHANT_CONTRACT_VERSION_0 = 0,
+ unsigned int inputs_len;
/**
- * Version 1
+ * List of outputs the merchant promises to yield (all of them) once
+ * the contract is paid.
*/
- TALER_MERCHANT_CONTRACT_VERSION_1 = 1
+ struct TALER_MERCHANT_ContractOutput *outputs;
+
+ /**
+ * Length of the @e outputs array.
+ */
+ unsigned int outputs_len;
};
+
/**
- * Possible token kinds.
+ * Order choice (part of the v1 order).
*/
-enum TALER_MERCHANT_ContractTokenKind
+struct TALER_MERCHANT_OrderChoice
{
+
/**
- * Token kind invalid
+ * Amount to be paid for this choice.
*/
- TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID = 0,
+ struct TALER_Amount amount;
/**
- * Subscription token kind
+ * Tip included by the customer (part of the total amount).
*/
- TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION = 1,
+ struct TALER_Amount tip;
/**
- * Discount token kind
+ * True if @e tip was not provided.
*/
- TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT = 2,
-};
+ bool no_tip;
-/**
- * Possible input types for the contract terms.
- */
-enum TALER_MERCHANT_ContractInputType
-{
+ /**
+ * Human readable description of the semantics of the choice within the
+ * contract to be shown to the user at payment.
+ */
+ char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized description.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Maximum fee the merchant is willing to pay for this choice.
+ * Set to an invalid amount to use instance defaults (zero or STEFAN).
+ */
+ 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.
+ */
+ struct TALER_MERCHANT_OrderInput *inputs;
/**
- * Input type invalid
+ * Length of the @e inputs array.
*/
- TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID = 0,
+ unsigned int inputs_len;
-#if FUTURE
/**
- * Input type coin
+ * List of outputs the merchant promises to yield (all of them) once
+ * the order is paid.
*/
- TALER_MERCHANT_CONTRACT_INPUT_TYPE_COIN = 1,
-#endif
+ struct TALER_MERCHANT_OrderOutput *outputs;
+
/**
- * Input type token
+ * Length of the @e outputs array.
*/
- TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN = 2
+ unsigned int outputs_len;
};
+
/**
- * Contract input (part of the v1 contract terms).
+ * Public key and corresponding metadata for a token family.
*/
-struct TALER_MERCHANT_ContractInput
+struct TALER_MERCHANT_ContractTokenFamilyKey
{
/**
- * Type of the input.
- */
- enum TALER_MERCHANT_ContractInputType type;
-
- union
- {
-#if FUTURE
- /**
- * Coin-based input (ration). (Future work, only here for reference)
- */
- struct
- {
- /**
- * Price to be paid.
- */
- struct TALER_Amount price;
-
- /**
- * Base URL of the ration authority.
- */
- const char *ration_authority_url;
- } coin;
-#endif
+ * Public key.
+ */
+ struct TALER_TokenIssuePublicKey pub;
- /**
- * Token-based input.
- */
- struct
- {
- /**
- * Slug of the token family to be used.
- */
- const char *token_family_slug;
+ /**
+ * Start time of the token family duration.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
- /**
- * Number of tokens of this type required. Defaults to one if the
- * field is not provided.
- */
- unsigned int count;
- } token;
- } details;
+ /**
+ * Tokens signed by this key will be valid until this time.
+ */
+ struct GNUNET_TIME_Timestamp valid_before;
};
+
/**
- * Possible output types for the contract terms.
+ * Represents a family of tokens issued by merchants that can be used in contracts.
*/
-enum TALER_MERCHANT_ContractOutputType
+struct TALER_MERCHANT_ContractTokenFamily
{
+ /**
+ * Slug of the token family.
+ */
+ char *slug;
/**
- * Invalid output type
+ * Human-readable name of the token family.
*/
- TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID = 0,
+ char *name;
/**
- * Output type token
+ * Human-readable description of the semantics of the tokens issued by
+ * this token family.
*/
- TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN = 1,
+ char *description;
/**
- * Output type donation-receipt
+ * Map from IETF BCP 47 language tags to localized description.
*/
- TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT = 2,
-#if FUTURE
+ json_t *description_i18n;
+
/**
- * Output type coin
+ * Relevant public keys of this token family for the given contract.
*/
- TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN = 3
-#endif
+ struct TALER_MERCHANT_ContractTokenFamilyKey *keys;
-};
+ /**
+ * Length of the @e keys array.
+ */
+ unsigned int keys_len;
-/**
- * Contract output (part of the v1 contract terms).
- */
-struct TALER_MERCHANT_ContractOutput
-{
/**
- * Type of the output.
+ * Must a wallet understand this token type to process contracts that
+ * consume or yield it?
*/
- enum TALER_MERCHANT_ContractOutputType type;
+ bool critical;
+
+ /**
+ * Kind of the token family.
+ */
+ enum TALER_MERCHANT_ContractTokenKind kind;
+ /**
+ * Kind-specific information about the token.
+ */
union
{
-#if FUTURE
- /**
- * Coin-based output.
- */
- struct
- {
- /**
- * Coins that will be yielded. This excludes any applicable withdraw fees.
- */
- struct TALER_Amount brutto_yield;
-
- /**
- * Base URL of the exchange that will issue the coins.
- *
- * NOTE: Once implemented, check if we need to allocate this here or if
- * we again reference the JSON as we do in other places.
- */
- char *exchange_url;
-
- } coin;
-#endif
/**
- * DONAU-receipt output.
+ * Subscription token.
*/
struct
{
/**
- * Amount of the donation. (optional)
- */
- struct TALER_Amount amount;
-
- /**
- * Base URLs of the donation authorities that will issue the tax receipt.
+ * Array of domain names where this subscription can be safely used
+ * (e.g. the issuer warrants that these sites will re-issue tokens of
+ * this type if the respective contract says so). May contain "*" for
+ * any domain or subdomain.
*/
- char **donau_urls;
+ char **trusted_domains;
/**
- * Length of the @e donau_urls array.
+ * Length of the @e trusted_domains array.
*/
- unsigned int donau_urls_len;
-
- } donation_receipt;
+ unsigned int trusted_domains_len;
+ } subscription;
/**
- * Token-based output.
- */
+ * Discount token.
+ */
struct
{
/**
- * Slug of the token family to be issued.
- * Note: this is a pointer into the JSON of the
- * respective contract/request and not owned here.
- */
- const char *token_family_slug;
-
- /**
- * Index of the public key in the @a token_family_slug's token family
- * ``keys`` array that this output token will have.
- */
- unsigned int key_index;
-
- /**
- * Number of tokens of this type required. Defaults to one if the
- * field is not provided.
+ * Array of domain names where this discount token is intended to be
+ * used. May contain "*" for any domain or subdomain. Users should be
+ * warned about sites proposing to consume discount tokens of this
+ * type that are not in this list that the merchant is accepting a
+ * coupon from a competitor and thus may be attaching different
+ * semantics (like get 20% discount for my competitors 30% discount
+ * token).
*/
- unsigned int count;
+ char **expected_domains;
/**
- * Determines when the output token should be valid.
- * Optional, set to zero for not specified (then we
- * use the current time).
+ * Length of the @e expected_domains array.
*/
- struct GNUNET_TIME_Timestamp valid_at;
-
- } token;
+ unsigned int expected_domains_len;
+ } discount;
} details;
-
};
/**
- * Contract choice (part of the v1 contract terms).
+ * Specifies the quantity of a product (to be) sold.
*/
-struct TALER_MERCHANT_ContractChoice
+struct TALER_MERCHANT_ProductQuantity
{
-
/**
- * Amount to be paid for this choice.
+ * Integer component of the quantity.
*/
- struct TALER_Amount amount;
+ uint64_t integer;
/**
- * Tip included by the customer (part of the total amount).
+ * Fractional component of the quantity, in the
+ * range of 0..TALER_MERCHANT_UNIT_FRAC_BASE-1.
*/
- struct TALER_Amount tip;
+ uint32_t fractional;
- /**
- * True if @e tip was not provided.
- */
- bool no_tip;
+};
+
+
+/**
+ * How to round when computing with amounts?
+ */
+enum TALER_MERCHANT_RoundMode
+{
+ TALER_MERCHANT_ROUND_NEAREST,
+ TALER_MERCHANT_ROUND_UP,
+ TALER_MERCHANT_ROUND_DOWN
+};
+
+
+/**
+ * Template type discriminator.
+ */
+enum TALER_MERCHANT_TemplateType
+{
+ TALER_MERCHANT_TEMPLATE_TYPE_INVALID = 0,
+ TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER,
+ TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART,
+ TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA
+};
+
+/**
+ * Determine template type from string.
+ *
+ * @param template_type string value (NULL means fixed-order)
+ * @return template type (defaults to fixed order)
+ */
+enum TALER_MERCHANT_TemplateType
+TALER_MERCHANT_template_type_from_string (const char *template_type);
+
+/**
+ * Convert template type to its string representation.
+ *
+ * @param template_type template type to convert
+ * @return string name or NULL for invalid types
+ */
+const char *
+TALER_MERCHANT_template_type_to_string (
+ enum TALER_MERCHANT_TemplateType template_type);
- /**
- * Human readable description of the semantics of the choice within the
- * contract to be shown to the user at payment.
- */
- char *description;
+/**
+ * Determine template type from a template contract.
+ *
+ * @param template_contract contract JSON
+ * @return template type (defaults to fixed order)
+ */
+enum TALER_MERCHANT_TemplateType
+TALER_MERCHANT_template_type_from_contract (const json_t *template_contract);
- /**
- * Map from IETF BCP 47 language tags to localized description.
- */
- json_t *description_i18n;
+/**
+ * Template contract fields for inventory templates.
+ */
+struct TALER_MERCHANT_TemplateContractInventory
+{
/**
- * Maximum fee the merchant is willing to pay for this choice.
- * Set to an invalid amount to use instance defaults (zero or STEFAN).
+ * Selected categories from the template contract.
*/
- struct TALER_Amount max_fee;
+ const json_t *selected_categories;
/**
- * List of inputs the wallet must provision (all of them) to satisfy the
- * conditions for the contract.
+ * Selected products from the template contract.
*/
- struct TALER_MERCHANT_ContractInput *inputs;
+ const json_t *selected_products;
/**
- * Length of the @e inputs array.
+ * Whether all products are selectable.
*/
- unsigned int inputs_len;
+ bool selected_all;
/**
- * List of outputs the merchant promises to yield (all of them) once
- * the contract is paid.
+ * Template requires exactly one selection.
*/
- struct TALER_MERCHANT_ContractOutput *outputs;
+ bool choose_one;
- /**
- * Length of the @e outputs array.
- */
- unsigned int outputs_len;
};
/**
- * Public key and corresponding metadata for a token family.
+ * Template contract fields for paivana templates.
*/
-struct TALER_MERCHANT_ContractTokenFamilyKey
+struct TALER_MERCHANT_TemplateContractPaivana
{
/**
- * Public key.
+ * Paivana website regular expression.
+ * NULL to allow any site.
*/
- struct TALER_TokenIssuePublicKey pub;
+ const char *website_regex;
/**
- * Start time of the token family duration.
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
*/
- struct GNUNET_TIME_Timestamp valid_after;
+ struct TALER_MERCHANT_OrderChoice *choices;
/**
- * Tokens signed by this key will be valid until this time.
+ * Length of the @e choices array.
*/
- struct GNUNET_TIME_Timestamp valid_before;
-};
+ unsigned int choices_len;
+};
/**
- * Represents a family of tokens issued by merchants that can be used in contracts.
+ * Parsed template contract.
*/
-struct TALER_MERCHANT_ContractTokenFamily
+struct TALER_MERCHANT_TemplateContract
{
/**
- * Slug of the token family.
+ * Template type.
*/
- char *slug;
+ enum TALER_MERCHANT_TemplateType type;
/**
- * Human-readable name of the token family.
+ * Summary from the template contract.
*/
- char *name;
+ const char *summary;
/**
- * Human-readable description of the semantics of the tokens issued by
- * this token family.
+ * Currency from the template contract.
*/
- char *description;
+ const char *currency;
/**
- * Map from IETF BCP 47 language tags to localized description.
+ * Amount from the template contract.
*/
- json_t *description_i18n;
+ struct TALER_Amount amount;
/**
- * Relevant public keys of this token family for the given contract.
+ * True if @e amount was not provided.
*/
- struct TALER_MERCHANT_ContractTokenFamilyKey *keys;
+ bool no_amount;
/**
- * Length of the @e keys array.
+ * Template allows tips.
*/
- unsigned int keys_len;
+ bool request_tip;
/**
- * Must a wallet understand this token type to process contracts that
- * consume or yield it?
+ * Minimum age required by the template.
*/
- bool critical;
+ uint32_t minimum_age;
/**
- * Kind of the token family.
+ * How long does the customer have to pay for the order.
+ * 0 if not specified (use instance default).
*/
- enum TALER_MERCHANT_ContractTokenKind kind;
+ struct GNUNET_TIME_Relative pay_duration;
/**
- * Kind-specific information about the token.
+ * How long does the user have at most to access/pickup the (digital)
+ * goods or service they bought. Optional, defaults to FOREVER.
*/
+ struct GNUNET_TIME_Relative max_pickup_duration;
+
union
{
+
/**
- * Subscription token.
+ * Parsed fields for inventory templates.
*/
- struct
- {
- /**
- * Array of domain names where this subscription can be safely used
- * (e.g. the issuer warrants that these sites will re-issue tokens of
- * this type if the respective contract says so). May contain "*" for
- * any domain or subdomain.
- */
- char **trusted_domains;
-
- /**
- * Length of the @e trusted_domains array.
- */
- unsigned int trusted_domains_len;
- } subscription;
+ struct TALER_MERCHANT_TemplateContractInventory inventory;
/**
- * Discount token.
- */
- struct
- {
- /**
- * Array of domain names where this discount token is intended to be
- * used. May contain "*" for any domain or subdomain. Users should be
- * warned about sites proposing to consume discount tokens of this
- * type that are not in this list that the merchant is accepting a
- * coupon from a competitor and thus may be attaching different
- * semantics (like get 20% discount for my competitors 30% discount
- * token).
- */
- char **expected_domains;
-
- /**
- * Length of the @e expected_domains array.
- */
- unsigned int expected_domains_len;
+ * Parsed fields for paivana templates.
+ */
+ struct TALER_MERCHANT_TemplateContractPaivana paivana;
- } discount;
} details;
+
+};
+
+/**
+ * Parse template contract JSON into @a out.
+ *
+ * @param template_contract JSON object containing the template contract
+ * @param[out] out parsed template contract
+ * @param[out] error_name pointer to the name of the failed field, or NULL
+ * @return #GNUNET_SYSERR if @a template_contract is malformed; #GNUNET_OK otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_template_contract_parse (
+ const json_t *template_contract,
+ struct TALER_MERCHANT_TemplateContract *out,
+ const char **error_name);
+
+
+/**
+ * Release memory from template contract @a tc.
+ * Does not free @a tc itself.
+ *
+ * @param[in] tc contract to free
+ */
+void
+TALER_MERCHANT_template_contract_free (
+ struct TALER_MERCHANT_TemplateContract *tc);
+
+
+/**
+ * Check if @a template_contract is valid.
+ *
+ * @param template_contract template contract to validate
+ * @return true if @a template_contract is valid
+ */
+bool
+TALER_MERCHANT_template_contract_valid (const json_t *template_contract);
+
+
+/**
+ * Convert critical operation enumeration value to string
+ * suitable for human readers.
+ *
+ * @param co input to convert
+ * @return operation value as string
+ */
+const char *
+TALER_MERCHANT_MFA_co2s (
+ enum TALER_MERCHANT_MFA_CriticalOperation co);
+
+
+/**
+ * Convert critical operation enumeration value to string.
+ *
+ * @param co input to convert
+ * @return operation value as string
+ */
+const char *
+TALER_MERCHANT_MFA_co_to_string (
+ enum TALER_MERCHANT_MFA_CriticalOperation co);
+
+
+/**
+ * Convert string to critical operation enumeration value.
+ *
+ * @param str input to convert
+ * @return #TALER_MERCHANT_MFA_CO_NONE on failure
+ */
+enum TALER_MERCHANT_MFA_CriticalOperation
+TALER_MERCHANT_MFA_co_from_string (const char *str);
+
+
+/**
+ * Convert MFA channel enumeration value to string.
+ *
+ * @param ch input to convert
+ * @return operation value as string
+ */
+const char *
+TALER_MERCHANT_MFA_channel_to_string (
+ enum TALER_MERCHANT_MFA_Channel ch);
+
+
+/**
+ * Convert string to MFA channel enumeration value.
+ *
+ * @param str input to convert
+ * @return #TALER_MERCHANT_MFA_CHANNEL_NONE on failure
+ */
+enum TALER_MERCHANT_MFA_Channel
+TALER_MERCHANT_MFA_channel_from_string (const char *str);
+
+
+/**
+ * @brief Salted hash of a body for a request that required MFA.
+ */
+struct TALER_MERCHANT_MFA_BodyHash
+{
+ /**
+ * Hash of the body and salt.
+ */
+ struct GNUNET_ShortHashCode hash;
};
+
/**
- * Specifies the quantity of a product (to be) sold.
+ * @brief Salt used when computing a `struct TALER_MERCHANT_MFA_BodyHash`
*/
-struct TALER_MERCHANT_ProductQuantity
+struct TALER_MERCHANT_MFA_BodySalt
{
/**
- * Integer component of the quantity.
+ * Salt.
*/
- uint64_t integer;
+ uint64_t salt[128 / 64];
+};
+
+/**
+ * @brief Token used to authorize report generation.
+ */
+struct TALER_MERCHANT_ReportToken
+{
/**
- * Fractional component of the quantity, in the
- * range of 0..TALER_MERCHANT_UNIT_FRAC_BASE-1.
+ * Salt.
*/
- uint32_t fractional;
-
+ uint64_t salt[256 / 64];
};
/**
- * How to round when computing with amounts?
+ * Hash the given request @a body with the given @a salt to
+ * produce @a h_body for MFA checks.
+ *
+ * @param body HTTP request body, NULL if body was empty
+ * @param salt salt to use
+ * @param h_body resulting hash
*/
-enum TALER_MERCHANT_RoundMode
-{
- TALER_MERCHANT_ROUND_NEAREST,
- TALER_MERCHANT_ROUND_UP,
- TALER_MERCHANT_ROUND_DOWN
-};
+void
+TALER_MERCHANT_mfa_body_hash (
+ const json_t *body,
+ const struct TALER_MERCHANT_MFA_BodySalt *salt,
+ struct TALER_MERCHANT_MFA_BodyHash *h_body);
/**
@@ -997,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
{
@@ -1085,61 +1304,54 @@ struct TALER_MERCHANT_ProductSold
/**
- * Struct to hold contract terms.
+ * Merchant information.
*/
-struct TALER_MERCHANT_Contract
+struct TALER_MERCHANT_MetaData
{
/**
- * URL where the same contract could be ordered again (if available).
+ * Legal name of the instance
*/
- char *public_reorder_url;
+ char *name;
/**
- * Our order ID.
+ * Merchant's site url
*/
- char *order_id;
+ char *website;
/**
- * Merchant base URL.
+ * Email contact for customers
*/
- char *merchant_base_url;
+ char *email;
/**
- * Merchant information.
+ * merchant's logo data uri
*/
- struct
- {
- /**
- * Legal name of the instance
- */
- char *name;
+ char *logo;
- /**
- * Merchant's site url
- */
- char *website;
+ /**
+ * Merchant address
+ */
+ json_t *address;
- /**
- * Email contact for customers
- */
- char *email;
+ /**
+ * Jurisdiction of the business
+ */
+ json_t *jurisdiction;
- /**
- * merchant's logo data uri
- */
- char *logo;
+};
- /**
- * Merchant address
- */
- json_t *address;
- /**
- * Jurisdiction of the business
- */
- json_t *jurisdiction;
+/**
+ * Struct to hold terms common to both orders and contracts and
+ * are basically present (or optional) all the time.
+ */
+struct TALER_MERCHANT_ContractBaseTerms
+{
- } merchant;
+ /**
+ * Version of the contract terms.
+ */
+ enum TALER_MERCHANT_ContractVersion version;
/**
* Summary of the contract.
@@ -1147,31 +1359,100 @@ struct TALER_MERCHANT_Contract
char *summary;
/**
- * Internationalized summary.
+ * Internationalized summary. Optional.
*/
json_t *summary_i18n;
/**
+ * URL where the same contract could be ordered again (if available).
+ * Optional.
+ */
+ 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;
/**
- * Length of the @e products array.
+ * Delivery location.
+ * Optional.
*/
- size_t products_len;
+ json_t *delivery_location;
+
+ /**
+ * Delivery date.
+ * Optional.
+ */
+ 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;
+
+ /**
+ * Extra data that is only interpreted by the merchant frontend.
+ */
+ json_t *extra;
+
+ /**
+ * Minimum age the buyer must have (in years).
+ */
+ uint8_t minimum_age;
+
+ /**
+ * Default money pot to use for this order, 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.
+ */
+ 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
+{
+
+ /**
+ * Shared base terms of contracts and orders.
+ */
+ struct TALER_MERCHANT_ContractBaseTerms *base;
+
+ /**
+ * Our order ID. Optional.
+ */
+ char *order_id;
/**
* Array of products that are part of the purchase.
@@ -1179,99 +1460,178 @@ struct TALER_MERCHANT_Contract
struct TALER_MERCHANT_ProductSold *products;
/**
+ * Length of the @e products array.
+ */
+ size_t products_len;
+
+ /**
* Timestamp of the contract.
+ * Optional. Set to "now" on parsing if not given.
*/
struct GNUNET_TIME_Timestamp timestamp;
/**
* Deadline for refunds.
+ * Optional. Set to FOREVER if not given.
*/
struct GNUNET_TIME_Timestamp refund_deadline;
/**
- * Specifies for how long the wallet should try to get an
- * automatic refund for the purchase.
- */
- struct GNUNET_TIME_Relative auto_refund;
-
- /**
* Payment deadline.
+ * Optional. Set to zero if not given.
*/
struct GNUNET_TIME_Timestamp pay_deadline;
/**
* Wire transfer deadline.
+ * Optional. Set to FOREVER if not given.
*/
- struct GNUNET_TIME_Timestamp wire_deadline;
+ struct GNUNET_TIME_Timestamp wire_transfer_deadline;
/**
- * Delivery date.
+ * Details depending on the @e base.version.
*/
- struct GNUNET_TIME_Timestamp delivery_date;
+ union
+ {
+
+ /**
+ * Details for v0 orders.
+ */
+ struct
+ {
+
+ /**
+ * Price to be paid for the transaction. Could be 0. The price is in addition
+ * to other instruments, such as rations and tokens.
+ * The exchange will subtract deposit fees from that amount
+ * before transferring it to the merchant.
+ */
+ struct TALER_Amount brutto;
+
+ /**
+ * Tip included by the customer (part of the total amount).
+ */
+ struct TALER_Amount tip;
+
+ /**
+ * True if @e tip was not provided.
+ */
+ bool no_tip;
+
+ /**
+ * 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;
+
+ /**
+ * Details for v1 contracts.
+ */
+ struct
+ {
+
+ /**
+ * Array of possible specific payment choices the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+2 * confirmation.
+ */
+ struct TALER_MERCHANT_OrderChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ } v1;
+
+ } details;
+
+};
+
+
+/**
+ * Struct to hold proto-contracts.
+ */
+struct TALER_MERCHANT_ProtoContract
+{
/**
- * 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.
+ * Shared base terms of contracts and orders.
*/
- struct GNUNET_TIME_Timestamp max_pickup_time;
+ struct TALER_MERCHANT_ContractBaseTerms *base;
/**
- * Merchant public key.
+ * Our order ID.
*/
- struct TALER_MerchantPublicKeyP merchant_pub;
+ char *order_id;
/**
- * The hash of the merchant instance's wire details.
+ * Timestamp of the contract.
*/
- struct TALER_MerchantWireHashP h_wire;
+ struct GNUNET_TIME_Timestamp timestamp;
/**
- * Wire transfer method identifier for the wire method associated with
- * @e h_wire.
+ * Deadline for refunds.
*/
- char *wire_method;
+ struct GNUNET_TIME_Timestamp refund_deadline;
/**
- * Exchanges that the merchant accepts even if it does not accept any auditors that audit them.
- * TODO: appropriate type
+ * Payment deadline.
*/
- json_t *exchanges;
+ struct GNUNET_TIME_Timestamp pay_deadline;
/**
- * Delivery location.
+ * Wire transfer deadline.
*/
- json_t *delivery_location;
+ struct GNUNET_TIME_Timestamp wire_deadline;
/**
- * Nonce generated by the wallet and echoed by the merchant
- * in this field when the proposal is generated.
+ * Merchant public key.
*/
- char *nonce;
+ struct TALER_MerchantPublicKeyP merchant_pub;
/**
- * Extra data that is only interpreted by the merchant frontend.
+ * Merchant base URL.
*/
- json_t *extra;
+ 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;
/**
- * Minimum age the buyer must have (in years).
+ * The hash of the merchant instance's wire details.
*/
- uint8_t minimum_age;
+ struct TALER_MerchantWireHashP h_wire;
/**
- * 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.
+ * Wire transfer method identifier for the wire method associated with
+ * @e h_wire.
*/
- uint64_t default_money_pot;
+ char *wire_method;
/**
- * Specified version of the contract.
+ * Exchanges that the merchant accepts even if it does not accept any auditors that audit them.
+ * TODO: appropriate type
*/
- enum TALER_MERCHANT_ContractVersion version;
+ json_t *exchanges;
/**
* Details depending on the @e version.
@@ -1304,8 +1664,8 @@ 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;
} v0;
@@ -1346,15 +1706,23 @@ struct TALER_MERCHANT_Contract
/**
- * Parse JSON contract terms in @a input.
- *
- * @param[in] input JSON object containing contract terms
- * @param nonce_optional whether `nonce' field is optional
- * @return parsed contract terms; NULL if @a input is malformed
+ * Struct to hold contract terms.
*/
-struct TALER_MERCHANT_Contract *
-TALER_MERCHANT_contract_parse (json_t *input,
- bool nonce_optional);
+struct TALER_MERCHANT_Contract
+{
+
+ /**
+ * 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;
+
+};
/**
@@ -1366,8 +1734,72 @@ TALER_MERCHANT_contract_parse (json_t *input,
* @return spec for parsing a contract input type
*/
struct GNUNET_JSON_Specification
-TALER_MERCHANT_json_spec_cit (const char *name,
- enum TALER_MERCHANT_ContractInputType *cit);
+TALER_MERCHANT_json_spec_cit (
+ const char *name,
+ enum TALER_MERCHANT_ContractInputType *cit);
+
+
+/**
+ * Create JSON specification to parse a merchant contract
+ * version.
+ *
+ * @param name name of the field
+ * @param[out] version where to write the contract version
+ * @return JSON specification object
+ */
+struct GNUNET_JSON_Specification
+TALER_MERCHANT_spec_contract_version (
+ const char *name,
+ enum TALER_MERCHANT_ContractVersion *version);
+
+
+/**
+ * Provide specification to parse given JSON object to merchant details in the
+ * order terms. All fields from @a order are copied.
+ *
+ * @param name name of the merchant details field in the JSON
+ * @param[out] order where the merchant details have to be written
+ */
+struct GNUNET_JSON_Specification
+TALER_MERCHANT_spec_merchant_details (
+ const char *name,
+ struct TALER_MERCHANT_MetaData *merchant);
+
+
+/**
+ * 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);
+
+
+/**
+ * Free representation of merchant details.
+ *
+ * @param[in,out] merchant metadata to release, excluding the pointer itself
+ */
+void
+TALER_MERCHANT_metadata_free (
+ struct TALER_MERCHANT_MetaData *merchant);
+
+
+/**
+ * Provide specification to parse given JSON array to order
+ * choices. All fields from @a choices elements are copied.
+ *
+ * @param name name of the choices field in the JSON
+ * @param[out] choices where the order choices array has to be written
+ * @param[out] choices_len length of the @a choices array
+ */
+struct GNUNET_JSON_Specification
+TALER_MERCHANT_spec_order_choices (
+ const char *name,
+ struct TALER_MERCHANT_OrderChoice **choices,
+ unsigned int *choices_len);
/**
@@ -1379,27 +1811,57 @@ TALER_MERCHANT_json_spec_cit (const char *name,
* @param[out] choices_len length of the @a choices array
*/
struct GNUNET_JSON_Specification
-TALER_MERCHANT_spec_choices (
+TALER_MERCHANT_spec_contract_choices (
const char *name,
struct TALER_MERCHANT_ContractChoice **choices,
unsigned int *choices_len);
/**
+ * Provide specification to parse given JSON array to token families in the
+ * contract terms. All fields from @a families items are copied.
+ *
+ * @param name name of the token families field in the JSON
+ * @param[out] families where the token families array has to be written
+ * @param[out] families_len length of the @a families array
+ */
+struct GNUNET_JSON_Specification
+TALER_MERCHANT_spec_token_families (
+ const char *name,
+ struct TALER_MERCHANT_ContractTokenFamily **families,
+ unsigned int *families_len);
+
+
+/**
* Parse JSON contract terms choice input.
*
* @param[in] root JSON object containing choice input
* @param[out] input parsed choice input, NULL if @a input is malformed
* @param index index of choice input in inputs array
- * @param order whether @a input is contained in order or contract terms
* @return #GNUNET_SYSERR if @a input is malformed; #GNUNET_OK otherwise
*/
+// FIXME: make static?
enum GNUNET_GenericReturnValue
-TALER_MERCHANT_parse_choice_input (
+TALER_MERCHANT_parse_contract_choice_input (
json_t *root,
struct TALER_MERCHANT_ContractInput *input,
- size_t index,
- bool order);
+ size_t index);
+
+
+/**
+ * Parse JSON order choice input.
+ *
+ * @param[in] root JSON object containing choice input
+ * @param[out] input parsed choice input, NULL if @a input is malformed
+ * @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,
+ struct TALER_MERCHANT_OrderInput *input,
+ size_t index);
/**
@@ -1411,20 +1873,8 @@ TALER_MERCHANT_parse_choice_input (
* entries are valid Taxes.
*/
bool
-TALER_MERCHANT_taxes_array_valid (const json_t *taxes);
-
-
-/**
- * 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);
+TALER_MERCHANT_taxes_array_valid (
+ const json_t *taxes);
/**
@@ -1436,8 +1886,9 @@ TALER_MERCHANT_parse_product_sold (const json_t *p,
* @return spec for parsing a contract output type
*/
struct GNUNET_JSON_Specification
-TALER_MERCHANT_json_spec_cot (const char *name,
- enum TALER_MERCHANT_ContractOutputType *cot);
+TALER_MERCHANT_json_spec_cot (
+ const char *name,
+ enum TALER_MERCHANT_ContractOutputType *cot);
/**
@@ -1446,52 +1897,75 @@ TALER_MERCHANT_json_spec_cot (const char *name,
* @param[in] root JSON object containing choice output
* @param[out] output parsed choice output, NULL if @a output is malformed
* @param index index of choice output in outputs array
- * @param order whether @a output is contained in order or contract terms
* @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise
*/
+// FIXME: make static?
enum GNUNET_GenericReturnValue
-TALER_MERCHANT_parse_choice_output (
+TALER_MERCHANT_parse_contract_choice_output (
json_t *root,
struct TALER_MERCHANT_ContractOutput *output,
- size_t index,
- bool order);
+ size_t index);
/**
- * Serialize @a p to JSON.
+ * Parse JSON order choice output.
*
- * @param p product to serialize
- * @return JSON object representing the product @a p
+ * @param[in] root JSON object containing choice output
+ * @param[out] output parsed choice output, NULL if @a output is malformed
+ * @param index index of choice output in outputs array
+ * @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise
*/
-json_t *
-TALER_MERCHANT_product_sold_serialize (
- const struct TALER_MERCHANT_ProductSold *p);
+// FIXME: make static?
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_parse_order_choice_output (
+ json_t *root,
+ struct TALER_MERCHANT_OrderOutput *output,
+ size_t index);
/**
- * Serialize contract terms into JSON object.
+ * Get JSON representation of contract choice.
*
- * @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 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,
- bool nonce_optional);
+TALER_MERCHANT_json_from_contract_choice (
+ const struct TALER_MERCHANT_ContractChoice *choice);
/**
- * Get JSON representation of contract 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 terms choice to free
+ */
+void
+TALER_MERCHANT_contract_choice_free (
+ struct TALER_MERCHANT_ContractChoice *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,
- bool order);
+TALER_MERCHANT_json_from_order_choice (
+ const struct TALER_MERCHANT_OrderChoice *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 terms choice to free
+ */
+void
+TALER_MERCHANT_order_choice_free (
+ struct TALER_MERCHANT_OrderChoice *choice);
/**
@@ -1527,14 +2001,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);
+
+
+/**
+ * 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
+ * @param allow_mip true to allow for MinimalInventoryProduct
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_parse_product_sold (
+ const json_t *p,
+ struct TALER_MERCHANT_ProductSold *r,
+ bool allow_mip);
+
+
+/**
+ * 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);
/**
@@ -1543,7 +2044,130 @@ TALER_MERCHANT_contract_choice_free (
* @param[in] product data structure to clean up
*/
void
-TALER_MERCHANT_product_sold_free (struct TALER_MERCHANT_ProductSold *product);
+TALER_MERCHANT_product_sold_free (
+ struct TALER_MERCHANT_ProductSold *product);
+
+
+/**
+ * Parse base terms (of orders and contracts) in @a input.
+ *
+ * @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_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
+
+
+/**
+ * Free the @a order and all fields in it.
+ *
+ * @param[in] order order to free
+ */
+void
+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
+ * @return NULL on failure
+ */
+struct TALER_MERCHANT_ProtoContract *
+TALER_MERCHANT_proto_contract_parse (
+ json_t *input);
+
+
+/**
+ * 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);
/**
@@ -1552,7 +2176,8 @@ TALER_MERCHANT_product_sold_free (struct TALER_MERCHANT_ProductSold *product);
* @param[in] contract contract to free
*/
void
-TALER_MERCHANT_contract_free (struct TALER_MERCHANT_Contract *contract);
+TALER_MERCHANT_contract_free (
+ struct TALER_MERCHANT_Contract *contract);
#endif
diff --git a/src/testing/test_merchant_mfa.sh b/src/testing/test_merchant_mfa.sh
@@ -60,7 +60,7 @@ echo -n "Self-provision instance ..."
STATUS=$(curl -H "Content-Type: application/json" -X POST \
http://localhost:9966/instances \
- -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"1234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
@@ -81,19 +81,19 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "200" ]
then
jq < "$LAST_RESPONSE"
- exit_fail "Expected 204 OK. Got: $STATUS"
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
echo "OK"
-TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1)
-ADDR=$(cat /tmp/test-merchant-sms-address.txt)
+TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}')
+ADDR=$(cat /tmp/test-merchant-email-address.txt)
-if [ "$ADDR" != "1234" ]
+if [ "$ADDR" != "self@example.com" ]
then
- exit_fail "Expected address '1234'. Got: $ADDR"
+ exit_fail "Expected address 'self@example.com'. Got: $ADDR"
fi
echo -n "Sending challenge $C1 solution "
@@ -120,14 +120,14 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "200" ]
then
jq < "$LAST_RESPONSE"
- exit_fail "Expected 204 OK. Got: $STATUS"
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
echo "OK"
-TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1)
+TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1 | awk '{print $1}')
echo -n "Sending challenge $C2 solution "
@@ -152,7 +152,7 @@ STATUS=$(curl \
-H "Taler-Challenge-Ids: $C1,$C2" \
-X POST \
http://localhost:9966/instances \
- -d '{"auth":{"method":"external"},"id":"self","name":"change","phone_number":"1234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -d '{"auth":{"method":"external"},"id":"self","name":"change","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
@@ -172,7 +172,7 @@ STATUS=$(curl \
-H "Taler-Challenge-Ids: $C1,$C2" \
-X POST \
http://localhost:9966/instances \
- -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"1234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
+ -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
@@ -202,7 +202,7 @@ fi
echo " OK"
-echo -n "Trigger MFA to add bank account with 2-FA authorization "
+echo -n "Do NOT Trigger MFA to add first bank account "
STATUS=$(curl \
-H "Content-Type: application/json" \
-X POST \
@@ -212,6 +212,25 @@ STATUS=$(curl \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
+if [ "$STATUS" != "200" ]
+then
+ jq < "$LAST_RESPONSE"
+ exit_fail "Expected 200 OK. Got: $STATUS"
+fi
+
+echo " OK"
+
+
+echo -n "Trigger MFA to add 2nd bank account with 2-FA authorization "
+STATUS=$(curl \
+ -H "Content-Type: application/json" \
+ -X POST \
+ -H 'Authorization: Bearer secret-token:pass1234' \
+ http://localhost:9966/instances/self/private/accounts \
+ -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \
+ -w "%{http_code}" -s \
+ -o "$LAST_RESPONSE")
+
if [ "$STATUS" != "202" ]
then
jq < "$LAST_RESPONSE"
@@ -221,8 +240,10 @@ fi
echo " OK"
-C1=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE")
+C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE")
+# Delete old TANs.
+rm /tmp/test-merchant-*-tan.txt
echo -n "Requesting challenge $C1 "
STATUS=$(curl -H "Content-Type: application/json" -X POST \
@@ -231,19 +252,19 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "200" ]
then
jq < "$LAST_RESPONSE"
- exit_fail "Expected 204 OK. Got: $STATUS"
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
echo "OK"
-TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1)
-ADDR=$(cat /tmp/test-merchant-sms-address.txt)
+TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}')
+ADDR=$(cat /tmp/test-merchant-email-address.txt)
-if [ "$ADDR" != "1234" ]
+if [ "$ADDR" != "self@example.com" ]
then
- exit_fail "Expected address '1234'. Got: $ADDR"
+ exit_fail "Expected address 'self@example.com'. Got: $ADDR"
fi
echo -n "Sending challenge $C1 solution "
@@ -263,14 +284,14 @@ then
fi
echo "OK"
-echo -n "Finally, add bank account "
+echo -n "Finally, add 2nd bank account "
STATUS=$(curl \
-X POST \
-H "Content-Type: application/json" \
-H "Taler-Challenge-Ids: $C1" \
-H 'Authorization: Bearer secret-token:pass1234' \
http://localhost:9966/instances/self/private/accounts \
- -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \
+ -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
@@ -311,19 +332,19 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "200" ]
then
jq < "$LAST_RESPONSE"
- exit_fail "Expected 204 OK. Got: $STATUS"
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
echo "OK"
-TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1)
-ADDR=$(cat /tmp/test-merchant-sms-address.txt)
+TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}')
+ADDR=$(cat /tmp/test-merchant-email-address.txt)
-if [ "$ADDR" != "1234" ]
+if [ "$ADDR" != "self@example.com" ]
then
- exit_fail "Expected address '1234'. Got: $ADDR"
+ exit_fail "Expected address 'self@example.com'. Got: $ADDR"
fi
echo -n "Sending challenge $C1 solution "
@@ -350,14 +371,14 @@ STATUS=$(curl -H "Content-Type: application/json" -X POST \
-w "%{http_code}" -s \
-o "$LAST_RESPONSE")
-if [ "$STATUS" != "204" ]
+if [ "$STATUS" != "200" ]
then
jq < "$LAST_RESPONSE"
- exit_fail "Expected 204 OK. Got: $STATUS"
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
echo "OK"
-TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1)
+TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1 | awk '{print $1}')
echo -n "Sending challenge $C2 solution "
diff --git a/src/testing/test_merchant_statistics.sh b/src/testing/test_merchant_statistics.sh
@@ -171,7 +171,7 @@ echo "OK"
echo -n "Creating money pot..."
STATUS=$(curl 'http://localhost:9966/private/pots' \
- -d '{"description":"First pot","pot_name":"#1"}' \
+ -d '{"description":"First pot","pot_name":"pot1"}' \
-w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "200" ]
@@ -247,7 +247,7 @@ echo "OK"
echo -n "Patching money pot ..."
STATUS=$(curl "http://localhost:9966/private/pots/$MONEY_POT" -X PATCH \
- -d '{"description":"Updated pot","expected_pot_totals":[],"new_pot_totals":["KUDOS:23"],"pot_name":"#1p"}' \
+ -d '{"description":"Updated pot","expected_pot_totals":[],"new_pot_totals":["KUDOS:23"],"pot_name":"pot1p"}' \
-w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
diff --git a/src/util/base_terms_parse.c b/src/util/base_terms_parse.c
@@ -0,0 +1,158 @@
+/*
+ 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);
+ struct GNUNET_JSON_Specification espec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ TALER_MERCHANT_spec_contract_version ("version",
+ &ct->version),
+ NULL),
+ 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_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 ("order_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;
+ ct->version = TALER_MERCHANT_CONTRACT_VERSION_0;
+ 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 ct;
+
+cleanup:
+ TALER_MERCHANT_base_terms_free (ct);
+ return NULL;
+}
+
+
+void
+TALER_MERCHANT_base_terms_free (
+ struct TALER_MERCHANT_ContractBaseTerms *ct)
+{
+ if (NULL == ct)
+ return;
+ GNUNET_free (ct->public_reorder_url);
+ 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;
+ }
+ 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,88 @@
+/*
+ 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_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 ("order_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,166 @@
+/*
+ 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),
+ 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
@@ -31,1010 +31,6 @@
/**
- * Parse merchant details of given JSON contract terms.
- *
- * @param cls closure, unused parameter
- * @param root the JSON object representing data
- * @param[out] ospec where to write the data
- * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
- */
-static enum GNUNET_GenericReturnValue
-parse_merchant_details (void *cls,
- json_t *root,
- struct GNUNET_JSON_Specification *ospec)
-{
- struct TALER_MERCHANT_Contract *contract = ospec->ptr;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string_copy ("name",
- &contract->merchant.name),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("email",
- &contract->merchant.email),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("website",
- &contract->merchant.website),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("logo",
- &contract->merchant.logo),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_copy ("address",
- &contract->merchant.address),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_copy ("jurisdiction",
- &contract->merchant.jurisdiction),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- const char *error_name;
- unsigned int error_line;
-
- (void) cls;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (root,
- spec,
- &error_name,
- &error_line))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to parse %s at %u: %s\n",
- spec[error_line].field,
- error_line,
- error_name);
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Provide specification to parse given JSON object to merchant details in the
- * contract terms. All fields from @a contract are copied.
- *
- * @param name name of the merchant details field in the JSON
- * @param[out] contract where the merchant details have to be written
- */
-static struct GNUNET_JSON_Specification
-spec_merchant_details (const char *name,
- struct TALER_MERCHANT_Contract *contract)
-{
- struct GNUNET_JSON_Specification ret = {
- .parser = &parse_merchant_details,
- .field = name,
- .ptr = contract,
- };
-
- return ret;
-}
-
-
-/**
- * Get enum value from contract token type string.
- *
- * @param str contract token type string
- * @return enum value of token type
- */
-static enum TALER_MERCHANT_ContractTokenKind
-contract_token_kind_from_string (const char *str)
-{
- if (0 == strcmp ("subscription",
- str))
- return TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION;
- if (0 == strcmp ("discount",
- str))
- return TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT;
- return TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_MERCHANT_parse_choice_input (
- json_t *root,
- struct TALER_MERCHANT_ContractInput *input,
- size_t index,
- bool order)
-{
- 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_choice_output (
- json_t *root,
- struct TALER_MERCHANT_ContractOutput *output,
- size_t index,
- bool order)
-{
- 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),
- (! order)
- ? GNUNET_JSON_spec_uint ("key_index",
- &output->details.token.key_index)
- : GNUNET_JSON_spec_end (),
- 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),
- (! order)
- ? GNUNET_JSON_spec_array_const ("donau_urls",
- &donau_urls)
- : GNUNET_JSON_spec_end (),
- 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_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_choice_input ((json_t *) jinput,
- &input,
- idx,
- false))
- {
- 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_choice_output ((json_t *) joutput,
- &output,
- idx,
- false))
- {
- 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_choices (
- const char *name,
- struct TALER_MERCHANT_ContractChoice **choices,
- unsigned int *choices_len)
-{
- struct GNUNET_JSON_Specification ret = {
- .cls = (void *) choices_len,
- .parser = &parse_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 token details of the given JSON contract token family.
- *
- * @param cls closure, unused parameter
- * @param root the JSON object representing data
- * @param[out] ospec where to write the data
- * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
- */
-static enum GNUNET_GenericReturnValue
-parse_token_details (void *cls,
- json_t *root,
- struct GNUNET_JSON_Specification *ospec)
-{
- struct TALER_MERCHANT_ContractTokenFamily *family = ospec->ptr;
- const char *class;
- const char *ename;
- unsigned int eline;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("class",
- &class),
- GNUNET_JSON_spec_end ()
- };
-
- (void) cls;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (root,
- ispec,
- &ename,
- &eline))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse %s at %u: %s\n",
- ispec[eline].field,
- eline,
- ename);
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- family->kind = contract_token_kind_from_string (class);
-
- switch (family->kind)
- {
- case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
- break;
- case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
- {
- const json_t *trusted_domains;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("trusted_domains",
- &trusted_domains),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (root,
- 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;
- }
-
- GNUNET_array_grow (family->details.subscription.trusted_domains,
- family->details.subscription.trusted_domains_len,
- json_array_size (trusted_domains));
-
- for (unsigned int i = 0;
- i < family->details.subscription.trusted_domains_len;
- i++)
- {
- const json_t *jdomain;
- jdomain = json_array_get (trusted_domains, i);
-
- if (! json_is_string (jdomain))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- family->details.subscription.trusted_domains[i] =
- GNUNET_strdup (json_string_value (jdomain));
- }
-
- return GNUNET_OK;
- }
- case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
- {
- const json_t *expected_domains;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("expected_domains",
- &expected_domains),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (root,
- 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;
- }
-
- GNUNET_array_grow (family->details.discount.expected_domains,
- family->details.discount.expected_domains_len,
- json_array_size (expected_domains));
-
- for (unsigned int i = 0;
- i < family->details.discount.expected_domains_len;
- i++)
- {
- const json_t *jdomain;
-
- jdomain = json_array_get (expected_domains,
- i);
- if (! json_is_string (jdomain))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- family->details.discount.expected_domains[i] =
- GNUNET_strdup (json_string_value (jdomain));
- }
-
- return GNUNET_OK;
- }
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Field 'type' invalid in token family\n");
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Provide specification to parse given JSON object to contract token details
- * in the contract token family. All fields from @a family are copied.
- *
- * @param name name of the token details field in the JSON
- * @param[out] family token_family where the token details have to be written
- */
-static struct GNUNET_JSON_Specification
-spec_token_details (const char *name,
- struct TALER_MERCHANT_ContractTokenFamily *family)
-{
- struct GNUNET_JSON_Specification ret = {
- .parser = &parse_token_details,
- .field = name,
- .ptr = family,
- };
-
- return ret;
-}
-
-
-/**
- * Parse given JSON object to token families array.
- *
- * @param cls closure, pointer to array length
- * @param root the json object representing the token families. The keys are
- * the token family slugs.
- * @param[out] ospec where to write the data
- * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
- */
-static enum GNUNET_GenericReturnValue
-parse_token_families (void *cls,
- json_t *root,
- struct GNUNET_JSON_Specification *ospec)
-{
- struct TALER_MERCHANT_ContractTokenFamily **families = ospec->ptr;
- unsigned int *families_len = cls;
- json_t *jfamily;
- const char *slug;
-
- if (! json_is_object (root))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- json_object_foreach (root, slug, jfamily)
- {
- const json_t *keys;
- struct TALER_MERCHANT_ContractTokenFamily family = {
- .slug = GNUNET_strdup (slug)
- };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string_copy ("name",
- &family.name),
- GNUNET_JSON_spec_string_copy ("description",
- &family.description),
- GNUNET_JSON_spec_object_copy ("description_i18n",
- &family.description_i18n),
- GNUNET_JSON_spec_array_const ("keys",
- &keys),
- spec_token_details ("details",
- &family),
- GNUNET_JSON_spec_bool ("critical",
- &family.critical),
- GNUNET_JSON_spec_end ()
- };
- const char *error_name;
- unsigned int error_line;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jfamily,
- spec,
- &error_name,
- &error_line))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to parse %s at %u: %s\n",
- spec[error_line].field,
- error_line,
- error_name);
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- GNUNET_array_grow (family.keys,
- family.keys_len,
- json_array_size (keys));
-
- for (unsigned int i = 0; i<family.keys_len; i++)
- {
- struct TALER_MERCHANT_ContractTokenFamilyKey *key = &family.keys[i];
- struct GNUNET_JSON_Specification key_spec[] = {
- TALER_JSON_spec_token_pub (
- NULL,
- &key->pub),
- GNUNET_JSON_spec_timestamp (
- "signature_validity_start",
- &key->valid_after),
- GNUNET_JSON_spec_timestamp (
- "signature_validity_end",
- &key->valid_before),
- GNUNET_JSON_spec_end ()
- };
- const char *ierror_name;
- unsigned int ierror_line;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (keys,
- i),
- key_spec,
- &ierror_name,
- &ierror_line))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to parse %s at %u: %s\n",
- key_spec[ierror_line].field,
- ierror_line,
- ierror_name);
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
-
- GNUNET_array_append (*families,
- *families_len,
- family);
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * Provide specification to parse given JSON array to token families in the
- * contract terms. All fields from @a families items are copied.
- *
- * @param name name of the token families field in the JSON
- * @param[out] families where the token families array has to be written
- * @param[out] families_len length of the @a families array
- */
-static struct GNUNET_JSON_Specification
-spec_token_families (
- const char *name,
- struct TALER_MERCHANT_ContractTokenFamily **families,
- unsigned int *families_len)
-{
- struct GNUNET_JSON_Specification ret = {
- .cls = (void *) families_len,
- .parser = &parse_token_families,
- .field = name,
- .ptr = families,
- };
-
- return ret;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_MERCHANT_find_token_family_key (
- const char *slug,
- struct GNUNET_TIME_Timestamp valid_after,
- const struct TALER_MERCHANT_ContractTokenFamily *families,
- unsigned int families_len,
- struct TALER_MERCHANT_ContractTokenFamily *family,
- struct TALER_MERCHANT_ContractTokenFamilyKey *key)
-{
- for (unsigned int i = 0; i < families_len; i++)
- {
- const struct TALER_MERCHANT_ContractTokenFamily *fami
- = &families[i];
-
- if (0 != strcmp (fami->slug,
- slug))
- continue;
- if (NULL != family)
- *family = *fami;
- for (unsigned int k = 0; k < fami->keys_len; k++)
- {
- struct TALER_MERCHANT_ContractTokenFamilyKey *ki = &fami->keys[k];
-
- if (GNUNET_TIME_timestamp_cmp (ki->valid_after,
- ==,
- valid_after))
- {
- if (NULL != key)
- *key = *ki;
- return GNUNET_OK;
- }
- }
- /* matching family found, but no key. */
- return GNUNET_NO;
- }
-
- /* no matching family found */
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Free all the fields in the given @a family, but not @a family itself, since
- * it is normally part of an array.
- *
- * @param[in] family contract token family to free
- */
-static void
-contract_token_family_free (
- struct TALER_MERCHANT_ContractTokenFamily *family)
-{
- GNUNET_free (family->slug);
- GNUNET_free (family->name);
- GNUNET_free (family->description);
- if (NULL != family->description_i18n)
- {
- json_decref (family->description_i18n);
- family->description_i18n = NULL;
- }
- for (unsigned int i = 0; i < family->keys_len; i++)
- TALER_token_issue_pub_free (&family->keys[i].pub);
- GNUNET_free (family->keys);
-
- switch (family->kind)
- {
- case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
- break;
- case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
- for (unsigned int i = 0; i < family->details.discount.expected_domains_len;
- i++)
- GNUNET_free (family->details.discount.expected_domains[i]);
- break;
- case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
- for (unsigned int i = 0; i < family->details.subscription.
- trusted_domains_len; i++)
- GNUNET_free (family->details.subscription.trusted_domains[i]);
- break;
- }
-}
-
-
-/**
- * Parse contract version of given JSON contract terms.
- *
- * @param cls closure, unused parameter
- * @param root the JSON object representing data
- * @param[out] spec where to write the data
- * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
- */
-static enum GNUNET_GenericReturnValue
-parse_contract_version (void *cls,
- json_t *root,
- struct GNUNET_JSON_Specification *spec)
-{
- enum TALER_MERCHANT_ContractVersion *res
- = (enum TALER_MERCHANT_ContractVersion *) spec->ptr;
-
- (void) cls;
- if (json_is_integer (root))
- {
- json_int_t version = json_integer_value (root);
-
- switch (version)
- {
- case 0:
- *res = TALER_MERCHANT_CONTRACT_VERSION_0;
- return GNUNET_OK;
- case 1:
- *res = TALER_MERCHANT_CONTRACT_VERSION_1;
- return GNUNET_OK;
- }
-
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (json_is_null (root))
- {
- *res = TALER_MERCHANT_CONTRACT_VERSION_0;
- return GNUNET_OK;
- }
-
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Create JSON specification to parse a merchant contract
- * version.
- *
- * @param name name of the field
- * @param[out] version where to write the contract version
- * @return JSON specification object
- */
-static struct GNUNET_JSON_Specification
-spec_contract_version (
- const char *name,
- enum TALER_MERCHANT_ContractVersion *version)
-{
- struct GNUNET_JSON_Specification ret = {
- .parser = &parse_contract_version,
- .field = name,
- .ptr = version
- };
-
- *version = TALER_MERCHANT_CONTRACT_VERSION_0;
- return ret;
-}
-
-
-/**
* Parse v0-specific fields of @a input JSON into @a contract.
*
* @param[in] input the JSON contract terms
@@ -1044,7 +40,7 @@ spec_contract_version (
static enum GNUNET_GenericReturnValue
parse_contract_v0 (
json_t *input,
- struct TALER_MERCHANT_Contract *contract)
+ struct TALER_MERCHANT_ProtoContract *contract)
{
struct GNUNET_JSON_Specification espec[] = {
TALER_JSON_spec_amount_any ("amount",
@@ -1099,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_choices (
+ TALER_MERCHANT_spec_contract_choices (
"choices",
- &contract->details.v1.choices,
- &contract->details.v1.choices_len),
- spec_token_families (
+ &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 ()
};
@@ -1143,447 +139,204 @@ parse_contract_v1 (
}
-/**
- * Parse the given unit quantity string @a s and store the result in @a q.
- *
- * @param s quantity to parse
- * @param[out] q where to store the result
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_unit_quantity (const char *s,
- struct TALER_MERCHANT_ProductQuantity *q)
+struct TALER_MERCHANT_ProtoContract *
+TALER_MERCHANT_proto_contract_parse (
+ json_t *input)
{
- const char *ptr;
- uint64_t integer = 0;
- uint32_t frac = 0;
- unsigned int digits = 0;
+ struct TALER_MERCHANT_ContractBaseTerms *base;
+ struct TALER_MERCHANT_ProtoContract *pc;
+ enum GNUNET_GenericReturnValue res;
- if (NULL == s)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- ptr = s;
- if ('\0' == *ptr)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if ('-' == *ptr)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (! isdigit ((unsigned char) *ptr))
+ base = TALER_MERCHANT_base_terms_parse (input);
+ if (NULL == base)
{
GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- while (isdigit ((unsigned char) *ptr))
- {
- unsigned int digit = (unsigned int) (*ptr - '0');
-
- /* We intentionally allow at most INT64_MAX (as -1 has special meanings),
- even though the data type would support UINT64_MAX */
- if (integer > (INT64_MAX - digit) / 10)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- integer = integer * 10 + digit;
- ptr++;
- }
- if ('.' == *ptr)
- {
- ptr++;
- if ('\0' == *ptr)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- while (isdigit ((unsigned char) *ptr))
- {
- unsigned int digit = (unsigned int) (*ptr - '0');
-
- if (digits >= TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- frac = (uint32_t) (frac * 10 + digit);
- digits++;
- ptr++;
- }
- while (digits < TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
- {
- frac *= 10;
- digits++;
- }
+ return NULL;
}
- if ('\0' != *ptr)
+ pc = GNUNET_new (struct TALER_MERCHANT_ProtoContract);
+ pc->base = base;
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- q->integer = integer;
- q->fractional = frac;
- return GNUNET_OK;
-}
-
-
-bool
-TALER_MERCHANT_taxes_array_valid (const json_t *taxes)
-{
- json_t *tax;
- size_t idx;
+ const json_t *products = NULL;
+ struct GNUNET_JSON_Specification espec[] = {
+ GNUNET_JSON_spec_string_copy ("order_id",
+ &pc->order_id),
+ 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),
- if (! json_is_array (taxes))
- return false;
- json_array_foreach (taxes, idx, tax)
- {
- struct TALER_Amount amount;
- const char *name;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("name",
- &name),
- TALER_JSON_spec_amount_any ("tax",
- &amount),
GNUNET_JSON_spec_end ()
};
- enum GNUNET_GenericReturnValue res;
const char *ename;
unsigned int eline;
- res = GNUNET_JSON_parse (tax,
- spec,
+ res = GNUNET_JSON_parse (input,
+ espec,
&ename,
&eline);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
- return false;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse proto contract at field %s\n",
+ ename);
+ goto cleanup;
}
- }
- return true;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_MERCHANT_parse_product_sold (const json_t *pj,
- struct TALER_MERCHANT_ProductSold *r)
-{
- bool no_quantity;
- bool no_unit_quantity;
- bool no_price;
- uint64_t legacy_quantity;
- const char *unit_quantity_s;
- struct TALER_Amount price;
- const json_t *prices = NULL;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("product_id",
- &r->product_id),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("product_name",
- &r->product_name),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("description",
- &r->description),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_copy ("description_i18n",
- &r->description_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("quantity",
- &legacy_quantity),
- &no_quantity),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("unit_quantity",
- &unit_quantity_s),
- &no_unit_quantity),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("unit",
- &r->unit),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any ("price",
- &price),
- &no_price),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("prices",
- &prices),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("image",
- &r->image),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_copy ("taxes",
- &r->taxes),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("delivery_date",
- &r->delivery_date),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("product_money_pot",
- &r->product_money_pot),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
- const char *ename;
- unsigned int eline;
-
- r->delivery_date = GNUNET_TIME_UNIT_FOREVER_TS;
- res = GNUNET_JSON_parse (pj,
- spec,
- &ename,
- &eline);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse product at field %s\n",
- ename);
- return GNUNET_SYSERR;
- }
- if (! no_quantity)
- {
- r->unit_quantity.integer = legacy_quantity;
- r->unit_quantity.fractional = 0;
- }
- if (! no_unit_quantity)
- {
- if (GNUNET_OK !=
- parse_unit_quantity (unit_quantity_s,
- &r->unit_quantity))
+ if (NULL != products)
{
- GNUNET_break (0);
- return GNUNET_SYSERR;
+ 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;
+ }
+ }
+ }
}
}
- if ( (! no_quantity) && (! no_unit_quantity) )
+ switch (base->version)
{
- GNUNET_break ( (0 == r->unit_quantity.fractional) &&
- (legacy_quantity == r->unit_quantity.integer) );
+ 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;
}
- if ( (NULL != r->image) &&
- (! TALER_MERCHANT_image_data_url_valid (r->image)) )
+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->base->version)
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ break;
+ case TALER_MERCHANT_CONTRACT_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 != r->taxes) &&
- (! TALER_MERCHANT_taxes_array_valid (r->taxes)) )
+ if (NULL != pc->products)
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ for (size_t i = 0; i<pc->products_len; i++)
+ TALER_MERCHANT_product_sold_free (&pc->products[i]);
+ GNUNET_free (pc->products);
}
- if (NULL != prices)
+ if (NULL != pc->base)
{
- size_t len = json_array_size (prices);
- size_t i;
- json_t *price_i;
-
- GNUNET_assert (len < UINT_MAX);
- r->prices = GNUNET_new_array ((unsigned int) len,
- struct TALER_Amount);
- json_array_foreach (prices, i, price_i)
- {
- struct GNUNET_JSON_Specification pspec[] = {
- TALER_JSON_spec_amount_any (NULL,
- &r->prices[i]),
- GNUNET_JSON_spec_end ()
- };
-
- res = GNUNET_JSON_parse (price_i,
- pspec,
- &ename,
- &eline);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse price at index %u\n",
- (unsigned int) i);
- return GNUNET_SYSERR;
- }
- }
+ TALER_MERCHANT_base_terms_free (pc->base);
+ pc->base = NULL;
}
- else if (! no_price)
+ GNUNET_free (pc->wire_method);
+ if (NULL != pc->exchanges)
{
- r->prices = GNUNET_new_array (1,
- struct TALER_Amount);
- r->prices[0] = price;
+ json_decref (pc->exchanges);
+ pc->exchanges = NULL;
}
- return GNUNET_OK;
+ GNUNET_free (pc->order_id);
+ GNUNET_free (pc);
}
struct TALER_MERCHANT_Contract *
-TALER_MERCHANT_contract_parse (json_t *input,
- bool nonce_optional)
+TALER_MERCHANT_contract_parse (json_t *input)
{
struct TALER_MERCHANT_Contract *contract
= GNUNET_new (struct TALER_MERCHANT_Contract);
- const json_t *products = NULL;
struct GNUNET_JSON_Specification espec[] = {
- spec_contract_version ("version",
- &contract->version),
- GNUNET_JSON_spec_string_copy ("summary",
- &contract->summary),
- /* FIXME: do i18n_str validation in the future */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_copy ("summary_i18n",
- &contract->summary_i18n),
- NULL),
- GNUNET_JSON_spec_string_copy ("order_id",
- &contract->order_id),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("public_reorder_url",
- &contract->public_reorder_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("fulfillment_url",
- &contract->fulfillment_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("fulfillment_message",
- &contract->fulfillment_message),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_copy ("fulfillment_message_i18n",
- &contract->fulfillment_message_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("products",
- &products),
- NULL),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &contract->timestamp),
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &contract->refund_deadline),
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &contract->pay_deadline),
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &contract->wire_deadline),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &contract->merchant_pub),
- GNUNET_JSON_spec_string_copy ("merchant_base_url",
- &contract->merchant_base_url),
- spec_merchant_details ("merchant",
- contract),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &contract->h_wire),
- GNUNET_JSON_spec_string_copy ("wire_method",
- &contract->wire_method),
- GNUNET_JSON_spec_array_copy ("exchanges",
- &contract->exchanges),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_copy ("delivery_location",
- &contract->delivery_location),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("delivery_date",
- &contract->delivery_date),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("max_pickup_time",
- &contract->max_pickup_time),
- NULL),
- (nonce_optional)
- ? GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string_copy ("nonce",
- &contract->nonce),
- NULL)
- : GNUNET_JSON_spec_string_copy ("nonce",
- &contract->nonce),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_relative_time ("auto_refund",
- &contract->auto_refund),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_copy ("extra",
- &contract->extra),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint8 ("minimum_age",
- &contract->minimum_age),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("default_money_pot",
- &contract->default_money_pot),
- NULL),
+ GNUNET_JSON_spec_string_copy ("nonce",
+ &contract->nonce),
GNUNET_JSON_spec_end ()
};
-
enum GNUNET_GenericReturnValue res;
const char *ename;
unsigned int eline;
GNUNET_assert (NULL != input);
- contract->max_pickup_time = GNUNET_TIME_UNIT_FOREVER_TS;
+ contract->pc = TALER_MERCHANT_proto_contract_parse (input);
+ if (NULL == contract->pc)
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (contract);
+ return NULL;
+ }
res = GNUNET_JSON_parse (input,
espec,
&ename,
&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;
}
- if (NULL != products)
- {
- contract->products_len = json_array_size (products);
- if (0 != contract->products_len)
- {
- size_t i;
- json_t *p;
-
- contract->products = GNUNET_new_array (contract->products_len,
- struct TALER_MERCHANT_ProductSold);
- json_array_foreach (products, i, p)
- {
- if (GNUNET_OK !=
- TALER_MERCHANT_parse_product_sold (p,
- &contract->products[i]))
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse product at offset %u\n",
- (unsigned int) i);
- goto cleanup;
- }
- }
- }
- }
- switch (contract->version)
- {
- case TALER_MERCHANT_CONTRACT_VERSION_0:
- 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:
@@ -1593,90 +346,16 @@ cleanup:
void
-TALER_MERCHANT_product_sold_free (struct TALER_MERCHANT_ProductSold *product)
-{
- GNUNET_free (product->product_id);
- GNUNET_free (product->product_name);
- GNUNET_free (product->description);
- json_decref (product->description_i18n);
- GNUNET_free (product->prices);
- GNUNET_free (product->unit);
- GNUNET_free (product->image);
- json_decref (product->taxes);
-}
-
-
-void
TALER_MERCHANT_contract_free (
struct TALER_MERCHANT_Contract *contract)
{
if (NULL == contract)
return;
- GNUNET_free (contract->public_reorder_url);
- GNUNET_free (contract->order_id);
- GNUNET_free (contract->merchant_base_url);
- GNUNET_free (contract->merchant.name);
- GNUNET_free (contract->merchant.website);
- GNUNET_free (contract->merchant.email);
- GNUNET_free (contract->merchant.logo);
- if (NULL != contract->merchant.address)
- {
- json_decref (contract->merchant.address);
- contract->merchant.address = NULL;
- }
- if (NULL != contract->merchant.jurisdiction)
- {
- json_decref (contract->merchant.jurisdiction);
- contract->merchant.jurisdiction = NULL;
- }
- GNUNET_free (contract->summary);
- GNUNET_free (contract->fulfillment_url);
- GNUNET_free (contract->fulfillment_message);
- if (NULL != contract->fulfillment_message_i18n)
- {
- json_decref (contract->fulfillment_message_i18n);
- contract->fulfillment_message_i18n = NULL;
- }
- if (NULL != contract->products)
- {
- for (size_t i = 0; i<contract->products_len; i++)
- TALER_MERCHANT_product_sold_free (&contract->products[i]);
- GNUNET_free (contract->products);
- }
- GNUNET_free (contract->wire_method);
- if (NULL != contract->exchanges)
- {
- json_decref (contract->exchanges);
- contract->exchanges = NULL;
- }
- if (NULL != contract->delivery_location)
+ if (NULL != contract->pc)
{
- json_decref (contract->delivery_location);
- contract->delivery_location = NULL;
+ TALER_MERCHANT_proto_contract_free (contract->pc);
+ contract->pc = NULL;
}
GNUNET_free (contract->nonce);
- if (NULL != contract->extra)
- {
- json_decref (contract->extra);
- contract->extra = NULL;
- }
-
- switch (contract->version)
- {
- 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++)
- contract_token_family_free (&contract->details.v1.token_authorities[i]);
- GNUNET_free (contract->details.v1.token_authorities);
- break;
- }
GNUNET_free (contract);
}
diff --git a/src/util/contract_serialize.c b/src/util/contract_serialize.c
@@ -19,7 +19,6 @@
* @author Iván Ávalos
* @author Christian Grothoff
*/
-
#include "platform.h"
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_common.h>
@@ -28,310 +27,6 @@
#include "taler/taler_util.h"
#include "taler/taler_merchant_util.h"
-/**
- * Get JSON representation of merchant details.
- *
- * @param[in] contract contract terms
- * @return JSON object with merchant details; NULL on error
- */
-static json_t *
-json_from_merchant_details (
- const struct TALER_MERCHANT_Contract *contract)
-{
- return GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("name",
- contract->merchant.name),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("email",
- contract->merchant.email)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("website",
- contract->merchant.website)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("logo",
- contract->merchant.logo)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("address",
- contract->merchant.address)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("jurisdiction",
- contract->merchant.jurisdiction)));
-}
-
-
-/**
- * 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,
- bool order)
-{
- 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)),
- (order)
- ? GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_amount (
- "max_fee",
- /* workaround for nullable amount */
- (GNUNET_OK ==
- TALER_amount_is_valid (&choice->max_fee))
- ? &choice->max_fee
- : NULL))
- : TALER_JSON_pack_amount ("max_fee",
- &choice->max_fee),
- (order)
- ? GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("inputs",
- inputs))
- : GNUNET_JSON_pack_array_steal ("inputs",
- inputs),
- (order)
- ? GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("outputs",
- outputs))
- : 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.
@@ -341,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",
@@ -364,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;
@@ -375,8 +70,7 @@ json_from_contract_v1 (
GNUNET_assert (0 == json_array_append_new (
choices,
TALER_MERCHANT_json_from_contract_choice (
- &input->details.v1.choices[i],
- false)));
+ &input->details.v1.choices[i])));
families = json_object ();
GNUNET_assert (0 != families);
@@ -396,206 +90,97 @@ 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)
-{
- 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,
- bool nonce_optional)
+TALER_MERCHANT_proto_contract_serialize (
+ const struct TALER_MERCHANT_ProtoContract *pc)
{
+ json_t *bj;
json_t *details;
json_t *products;
- switch (input->version)
+ bj = TALER_MERCHANT_base_terms_serialize (pc->base);
+ switch (pc->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;
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"unknown contract type version %d",
- input->version);
+ pc->base->version);
GNUNET_assert (0);
return NULL;
success:
products = json_array ();
GNUNET_assert (NULL != products);
- for (size_t i = 0; i<input->products_len; i++)
+ for (size_t i = 0; i<pc->products_len; i++)
{
GNUNET_assert (
0 ==
json_array_append_new (products,
TALER_MERCHANT_product_sold_serialize (
- &input->products[i])));
+ &pc->products[i])));
}
return GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("version",
- input->version),
- GNUNET_JSON_pack_string ("summary",
- input->summary),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("summary_i18n",
- input->summary_i18n)),
GNUNET_JSON_pack_string ("order_id",
- input->order_id),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("public_reorder_url",
- input->public_reorder_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("fulfillment_url",
- input->fulfillment_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("fulfillment_message",
- input->fulfillment_message)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
- input->fulfillment_message_i18n)),
- GNUNET_JSON_pack_array_steal ("products",
- products),
+ pc->order_id),
+ GNUNET_JSON_pack_object_steal (NULL,
+ bj),
GNUNET_JSON_pack_timestamp ("timestamp",
- input->timestamp),
+ pc->timestamp),
GNUNET_JSON_pack_timestamp ("refund_deadline",
- input->refund_deadline),
+ pc->refund_deadline),
GNUNET_JSON_pack_timestamp ("pay_deadline",
- input->pay_deadline),
+ pc->pay_deadline),
GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
- input->wire_deadline),
+ pc->wire_deadline),
GNUNET_JSON_pack_data_auto ("merchant_pub",
- &input->merchant_pub.eddsa_pub),
+ &pc->merchant_pub.eddsa_pub),
GNUNET_JSON_pack_string ("merchant_base_url",
- input->merchant_base_url),
- GNUNET_JSON_pack_object_steal ("merchant",
- json_from_merchant_details (input)),
+ pc->merchant_base_url),
+ GNUNET_JSON_pack_object_steal (
+ "merchant",
+ TALER_MERCHANT_metadata_to_json (
+ &pc->merchant)),
+ GNUNET_JSON_pack_array_steal ("products",
+ products),
GNUNET_JSON_pack_data_auto ("h_wire",
- &input->h_wire),
+ &pc->h_wire),
GNUNET_JSON_pack_string ("wire_method",
- input->wire_method),
+ pc->wire_method),
GNUNET_JSON_pack_array_steal ("exchanges",
- input->exchanges),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("delivery_location",
- input->delivery_location)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("delivery_date",
- input->delivery_date)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_TIME_absolute_is_never (input->max_pickup_time.abs_time)
- ? GNUNET_JSON_pack_string ("dummy",
- NULL)
- : GNUNET_JSON_pack_timestamp ("max_pickup_time",
- input->max_pickup_time)),
- (nonce_optional)
- ? GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("nonce",
- input->nonce))
- : GNUNET_JSON_pack_string ("nonce",
- input->nonce),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_time_rel ("auto_refund",
- input->auto_refund)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("extra",
- input->extra)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_uint64 ("minimum_age",
- input->minimum_age)),
- (0 == input->default_money_pot)
- ? GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("dummy",
- NULL))
- : GNUNET_JSON_pack_uint64 ("default_money_pot",
- input->default_money_pot),
+ pc->exchanges),
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_object_steal (NULL,
+ pj));
+}
diff --git a/src/util/contract_version_parse.c b/src/util/contract_version_parse.c
@@ -0,0 +1,84 @@
+/*
+ This file is part of TALER
+ (C) 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_version_parse.c
+ * @brief parser for the contract version
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <string.h>
+#include "taler/taler_merchant_util.h"
+
+/**
+ * Parse contract version of given JSON contract terms.
+ *
+ * @param cls closure, unused parameter
+ * @param root the JSON object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_contract_version (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ enum TALER_MERCHANT_ContractVersion *res
+ = (enum TALER_MERCHANT_ContractVersion *) spec->ptr;
+
+ (void) cls;
+ if (json_is_integer (root))
+ {
+ json_int_t version = json_integer_value (root);
+
+ switch (version)
+ {
+ case 0:
+ *res = TALER_MERCHANT_CONTRACT_VERSION_0;
+ return GNUNET_OK;
+ case 1:
+ *res = TALER_MERCHANT_CONTRACT_VERSION_1;
+ return GNUNET_OK;
+ }
+
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (json_is_null (root))
+ {
+ *res = TALER_MERCHANT_CONTRACT_VERSION_0;
+ return GNUNET_OK;
+ }
+
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_MERCHANT_spec_contract_version (
+ const char *name,
+ enum TALER_MERCHANT_ContractVersion *version)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_contract_version,
+ .field = name,
+ .ptr = version
+ };
+
+ *version = TALER_MERCHANT_CONTRACT_VERSION_0;
+ return ret;
+}
diff --git a/src/util/merchant_parse.c b/src/util/merchant_parse.c
@@ -0,0 +1,152 @@
+/*
+ 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/merchant_parse.c
+ * @brief shared logic for merchant metadata 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"
+
+
+/**
+ * Parse merchant details of given JSON order terms.
+ *
+ * @param cls closure, unused parameter
+ * @param root the JSON object representing data
+ * @param[out] ospec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_merchant_details (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *ospec)
+{
+ struct TALER_MERCHANT_MetaData *merchant = ospec->ptr;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string_copy ("name",
+ &merchant->name),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("email",
+ &merchant->email),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("website",
+ &merchant->website),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("logo",
+ &merchant->logo),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_copy ("address",
+ &merchant->address),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_copy ("jurisdiction",
+ &merchant->jurisdiction),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *error_name;
+ unsigned int error_line;
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[error_line].field,
+ error_line,
+ error_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+json_t *
+TALER_MERCHANT_metadata_to_json (
+ const struct TALER_MERCHANT_MetaData *merchant)
+{
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ merchant->name),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("email",
+ merchant->email)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("website",
+ merchant->website)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("logo",
+ merchant->logo)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("address",
+ merchant->address)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("jurisdiction",
+ merchant->jurisdiction)));
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_MERCHANT_spec_merchant_details (
+ const char *name,
+ struct TALER_MERCHANT_MetaData *merchant)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_merchant_details,
+ .field = name,
+ .ptr = merchant,
+ };
+
+ return ret;
+}
+
+
+void
+TALER_MERCHANT_metadata_free (
+ struct TALER_MERCHANT_MetaData *merchant)
+{
+ GNUNET_free (merchant->name);
+ GNUNET_free (merchant->website);
+ GNUNET_free (merchant->email);
+ GNUNET_free (merchant->logo);
+ if (NULL != merchant->address)
+ {
+ json_decref (merchant->address);
+ merchant->address = NULL;
+ }
+ if (NULL != merchant->jurisdiction)
+ {
+ json_decref (merchant->jurisdiction);
+ merchant->jurisdiction = NULL;
+ }
+}
diff --git a/src/util/meson.build b/src/util/meson.build
@@ -15,14 +15,28 @@ endforeach
libtalermerchantutil_SOURCES = [
'amount_quantity.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_choice_serialize.c',
+ 'order_parse.c',
'os_installation.c',
+ 'product_parse.c',
+ 'product_sold_serialize.c',
'template_parse.c',
- 'value_kinds.c',
+ 'token_family_parse.c',
+ 'token_family_serialize.c',
+ 'util.c',
'validators.c',
+ 'value_kinds.c',
]
libtalermerchantutil = library(
diff --git a/src/util/order_choice_parse.c b/src/util/order_choice_parse.c
@@ -0,0 +1,444 @@
+/*
+ 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:
+ {
+ 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;
+ }
+ 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_fee) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&choice->amount,
+ &choice->max_fee)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fee currency does not match amount currency in choice #%u\n",
+ i);
+ 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:
+ 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_choice_serialize.c b/src/util/order_choice_serialize.c
@@ -0,0 +1,153 @@
+/*
+ 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/order_choice_serialize.c
+ * @brief shared logic for order 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 order choice input.
+ *
+ * @param[in] input order terms choice input
+ * @return JSON representation of @a input; NULL on error
+ */
+static json_t *
+json_from_order_input (
+ const struct TALER_MERCHANT_OrderInput *input)
+{
+ switch (input->type)
+ {
+ case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "invalid order 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 order input type %d",
+ input->type);
+ GNUNET_assert (0);
+ return NULL;
+}
+
+
+/**
+ * Get JSON representation of order choice output.
+ *
+ * @param[in] output order terms choice output
+ * @return JSON representation of @a output; NULL on error
+ */
+static json_t *
+json_from_order_output (
+ const struct TALER_MERCHANT_OrderOutput *output)
+{
+ switch (output->type)
+ {
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "invalid order 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));
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "tax-receipt"),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("amount",
+ output->details.donation_receipt.no_amount
+ ? NULL
+ : &output->details.donation_receipt.amount)));
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unsupported order output type %d",
+ output->type);
+ GNUNET_assert (0);
+ return NULL;
+}
+
+
+json_t *
+TALER_MERCHANT_json_from_order_choice (
+ const struct TALER_MERCHANT_OrderChoice *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_order_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_order_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)),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("max_fee",
+ choice->no_max_fee
+ ? NULL
+ : &choice->max_fee)),
+ GNUNET_JSON_pack_array_steal ("inputs",
+ inputs),
+ GNUNET_JSON_pack_array_steal ("outputs",
+ outputs));
+}
diff --git a/src/util/order_parse.c b/src/util/order_parse.c
@@ -0,0 +1,290 @@
+/*
+ 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_parse.c
+ * @brief shared logic for order 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"
+
+
+/**
+ * Parse v0-specific fields of @a input JSON into @a order.
+ *
+ * @param[in] input the JSON order terms
+ * @param[out] order where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_order_v0 (
+ json_t *input,
+ struct TALER_MERCHANT_Order *order)
+{
+ struct GNUNET_JSON_Specification espec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &order->details.v0.brutto),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("tip",
+ &order->details.v0.tip),
+ &order->details.v0.no_tip),
+ 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;
+ const char *ename;
+ unsigned int eline;
+
+ res = GNUNET_JSON_parse (input,
+ espec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse order v0 at field %s\n",
+ ename);
+ return GNUNET_SYSERR;
+ }
+
+ 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,
+ "'max_fee' in database does not match currency of order price");
+ return GNUNET_SYSERR;
+ }
+ if ( (! order->details.v0.no_tip) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&order->details.v0.tip,
+ &order->details.v0.brutto)) )
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "'tip' in database does not match currency of order price");
+ return GNUNET_SYSERR;
+ }
+
+ return res;
+}
+
+
+/**
+ * Parse v1-specific fields of @a input JSON into @a order.
+ *
+ * @param[in] input the JSON order terms
+ * @param[out] order where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_order_v1 (
+ json_t *input,
+ struct TALER_MERCHANT_Order *order)
+{
+ struct GNUNET_JSON_Specification espec[] = {
+ TALER_MERCHANT_spec_order_choices (
+ "choices",
+ &order->details.v1.choices,
+ &order->details.v1.choices_len),
+ GNUNET_JSON_spec_end ()
+ };
+
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
+
+ res = GNUNET_JSON_parse (input,
+ espec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse order v1 at field %s\n",
+ ename);
+ return GNUNET_SYSERR;
+ }
+
+ return res;
+}
+
+
+struct TALER_MERCHANT_Order *
+TALER_MERCHANT_order_parse (json_t *input)
+{
+ struct TALER_MERCHANT_Order *order
+ = GNUNET_new (struct TALER_MERCHANT_Order);
+ enum GNUNET_GenericReturnValue res
+ = GNUNET_SYSERR;
+
+ GNUNET_assert (NULL != input);
+ order->base = TALER_MERCHANT_base_terms_parse (input);
+ if (NULL == order->base)
+ {
+ GNUNET_break (0);
+ GNUNET_free (order);
+ return NULL;
+ }
+ order->refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+ order->wire_transfer_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+ {
+ const 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_string_copy ("order_id",
+ &order->order_id),
+ 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_transfer_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,
+ order);
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ res = parse_order_v1 (input,
+ order);
+ break;
+ }
+
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse order\n");
+ goto cleanup;
+ }
+ return order;
+
+cleanup:
+ TALER_MERCHANT_order_free (order);
+ return NULL;
+}
+
+
+void
+TALER_MERCHANT_order_free (
+ struct TALER_MERCHANT_Order *order)
+{
+ if (NULL == order)
+ return;
+ switch (order->base->version)
+ {
+ case TALER_MERCHANT_CONTRACT_VERSION_0:
+ break;
+ case TALER_MERCHANT_CONTRACT_VERSION_1:
+ for (unsigned int i = 0;
+ i < order->details.v1.choices_len;
+ i++)
+ TALER_MERCHANT_order_choice_free (&order->details.v1.choices[i]);
+ GNUNET_free (order->details.v1.choices);
+ break;
+ }
+ 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 = 0;
+ }
+ GNUNET_free (order->order_id);
+ if (NULL != order->base)
+ {
+ TALER_MERCHANT_base_terms_free (order->base);
+ order->base = NULL;
+ }
+ GNUNET_free (order);
+}
diff --git a/src/util/product_parse.c b/src/util/product_parse.c
@@ -0,0 +1,285 @@
+/*
+ This file is part of TALER
+ (C) 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/product_parse.c
+ * @brief small helpers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <string.h>
+#include "taler/taler_merchant_util.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <taler/taler_json_lib.h>
+
+/**
+ * Parse the given unit quantity string @a s and store the result in @a q.
+ *
+ * @param s quantity to parse
+ * @param[out] q where to store the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_unit_quantity (const char *s,
+ struct TALER_MERCHANT_ProductQuantity *q)
+{
+ const char *ptr;
+ uint64_t integer = 0;
+ uint32_t frac = 0;
+ unsigned int digits = 0;
+
+ if (NULL == s)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ptr = s;
+ if ('\0' == *ptr)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ('-' == *ptr)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! isdigit ((unsigned char) *ptr))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while (isdigit ((unsigned char) *ptr))
+ {
+ unsigned int digit = (unsigned int) (*ptr - '0');
+
+ /* We intentionally allow at most INT64_MAX (as -1 has special meanings),
+ even though the data type would support UINT64_MAX */
+ if (integer > (INT64_MAX - digit) / 10)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ integer = integer * 10 + digit;
+ ptr++;
+ }
+ if ('.' == *ptr)
+ {
+ ptr++;
+ if ('\0' == *ptr)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while (isdigit ((unsigned char) *ptr))
+ {
+ unsigned int digit = (unsigned int) (*ptr - '0');
+
+ if (digits >= TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ frac = (uint32_t) (frac * 10 + digit);
+ digits++;
+ ptr++;
+ }
+ while (digits < TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS)
+ {
+ frac *= 10;
+ digits++;
+ }
+ }
+ if ('\0' != *ptr)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ q->integer = integer;
+ q->fractional = frac;
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_parse_product_sold (const json_t *pj,
+ struct TALER_MERCHANT_ProductSold *r,
+ bool allow_mip)
+{
+ // FIXME: properly implement distinction between MIP and non-MIP!
+ bool no_quantity;
+ bool no_unit_quantity;
+ bool no_price;
+ uint64_t legacy_quantity;
+ const char *unit_quantity_s;
+ struct TALER_Amount price;
+ const json_t *prices = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("product_id",
+ &r->product_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("product_name",
+ &r->product_name),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("description",
+ &r->description),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_copy ("description_i18n",
+ &r->description_i18n),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("quantity",
+ &legacy_quantity),
+ &no_quantity),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("unit_quantity",
+ &unit_quantity_s),
+ &no_unit_quantity),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("unit",
+ &r->unit),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("price",
+ &price),
+ &no_price),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("prices",
+ &prices),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string_copy ("image",
+ &r->image),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_copy ("taxes",
+ &r->taxes),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("delivery_date",
+ &r->delivery_date),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("product_money_pot",
+ &r->product_money_pot),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
+
+ r->delivery_date = GNUNET_TIME_UNIT_FOREVER_TS;
+ res = GNUNET_JSON_parse (pj,
+ spec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse product at field %s\n",
+ ename);
+ return GNUNET_SYSERR;
+ }
+ if (! no_quantity)
+ {
+ r->unit_quantity.integer = legacy_quantity;
+ r->unit_quantity.fractional = 0;
+ }
+ if (! no_unit_quantity)
+ {
+ if (GNUNET_OK !=
+ parse_unit_quantity (unit_quantity_s,
+ &r->unit_quantity))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ if ( (! no_quantity) && (! no_unit_quantity) )
+ {
+ GNUNET_break ( (0 == r->unit_quantity.fractional) &&
+ (legacy_quantity == r->unit_quantity.integer) );
+ }
+ if ( (NULL != r->image) &&
+ (! TALER_MERCHANT_image_data_url_valid (r->image)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (NULL != r->taxes) &&
+ (! TALER_MERCHANT_taxes_array_valid (r->taxes)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL != prices)
+ {
+ size_t len = json_array_size (prices);
+ size_t i;
+ json_t *price_i;
+
+ GNUNET_assert (len < UINT_MAX);
+ r->prices = GNUNET_new_array ((unsigned int) len,
+ struct TALER_Amount);
+ json_array_foreach (prices, i, price_i)
+ {
+ struct GNUNET_JSON_Specification pspec[] = {
+ TALER_JSON_spec_amount_any (NULL,
+ &r->prices[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = GNUNET_JSON_parse (price_i,
+ pspec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse price at index %u\n",
+ (unsigned int) i);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ else if (! no_price)
+ {
+ r->prices = GNUNET_new_array (1,
+ struct TALER_Amount);
+ r->prices[0] = price;
+ }
+ return GNUNET_OK;
+}
+
+
+void
+TALER_MERCHANT_product_sold_free (struct TALER_MERCHANT_ProductSold *product)
+{
+ GNUNET_free (product->product_id);
+ GNUNET_free (product->product_name);
+ GNUNET_free (product->description);
+ json_decref (product->description_i18n);
+ GNUNET_free (product->prices);
+ GNUNET_free (product->unit);
+ GNUNET_free (product->image);
+ json_decref (product->taxes);
+}
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 ()
};
@@ -194,9 +198,9 @@ parse_template_paivana (const json_t *template_contract,
GNUNET_JSON_spec_string ("website_regex",
&out->details.paivana.website_regex),
NULL),
- TALER_MERCHANT_spec_choices ("choices",
- &out->details.paivana.choices,
- &out->details.paivana.choices_len),
+ TALER_MERCHANT_spec_order_choices ("choices",
+ &out->details.paivana.choices,
+ &out->details.paivana.choices_len),
GNUNET_JSON_spec_end ()
};
const char *en;
@@ -348,7 +352,7 @@ TALER_MERCHANT_template_contract_free (
return;
case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++)
- TALER_MERCHANT_contract_choice_free (&tc->details.paivana.choices[i]);
+ TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]);
GNUNET_array_grow (tc->details.paivana.choices,
tc->details.paivana.choices_len,
0);
diff --git a/src/util/test_contract.c b/src/util/test_contract.c
@@ -61,7 +61,7 @@ const char *contract_common = "{\n"
" \"exchanges\": [],\n"
" \"delivery_location\": {},\n" // TODO
" \"delivery_date\": {\"t_s\": 1736174497},\n"
- " \"nonce\": \"\",\n" // TODO
+ " \"nonce\": \"test-nonce\",\n"
" \"auto_refund\": {\"d_us\": \"forever\"},\n"
" \"extra\": {\n"
" \"key0\": \"value0\",\n"
@@ -181,12 +181,11 @@ main (int argc,
GNUNET_assert (0 == json_object_update_new (v0, common));
- v0_parsed = TALER_MERCHANT_contract_parse (v0, false);
+ v0_parsed = TALER_MERCHANT_contract_parse (v0);
GNUNET_assert (NULL != v0_parsed);
- v0_serialized = TALER_MERCHANT_contract_serialize (v0_parsed, false);
+ v0_serialized = TALER_MERCHANT_contract_serialize (v0_parsed);
GNUNET_assert (NULL != v0_serialized);
-
GNUNET_assert (1 == json_equal (v0, v0_serialized));
json_decref (v0_serialized);
@@ -208,20 +207,20 @@ main (int argc,
GNUNET_assert (0 == json_object_update_new (v1, common));
- v1_parsed = TALER_MERCHANT_contract_parse (v1, false);
+ v1_parsed = TALER_MERCHANT_contract_parse (v1);
GNUNET_assert (NULL != v1_parsed);
- v1_serialized = TALER_MERCHANT_contract_serialize (v1_parsed, false);
+ v1_serialized = TALER_MERCHANT_contract_serialize (v1_parsed);
GNUNET_assert (NULL != v1_serialized);
GNUNET_assert (1 == json_equal (v1, v1_serialized));
for (unsigned int i = 0;
- i < v1_parsed->details.v1.token_authorities_len;
+ i < v1_parsed->pc->details.v1.token_authorities_len;
i++)
{
struct TALER_MERCHANT_ContractTokenFamily *in =
- &v1_parsed->details.v1.token_authorities[i];
+ &v1_parsed->pc->details.v1.token_authorities[i];
for (unsigned int j = 0; j < in->keys_len; j++)
{
struct TALER_MERCHANT_ContractTokenFamilyKey *inkey = &in->keys[j];
@@ -230,8 +229,8 @@ main (int argc,
GNUNET_assert (GNUNET_OK == TALER_MERCHANT_find_token_family_key (
in->slug,
inkey->valid_after,
- v1_parsed->details.v1.token_authorities,
- v1_parsed->details.v1.token_authorities_len,
+ v1_parsed->pc->details.v1.token_authorities,
+ v1_parsed->pc->details.v1.token_authorities_len,
&out,
&outkey));
GNUNET_assert (0 == GNUNET_memcmp (in, &out));
diff --git a/src/util/token_family_parse.c b/src/util/token_family_parse.c
@@ -0,0 +1,399 @@
+/*
+ This file is part of TALER
+ (C) 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/token_family_parse.c
+ * @brief parser for data about token families
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <string.h>
+#include "taler/taler_merchant_util.h"
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+
+
+void
+TALER_MERCHANT_contract_token_family_free (
+ struct TALER_MERCHANT_ContractTokenFamily *family)
+{
+ GNUNET_free (family->slug);
+ GNUNET_free (family->name);
+ GNUNET_free (family->description);
+ if (NULL != family->description_i18n)
+ {
+ json_decref (family->description_i18n);
+ family->description_i18n = NULL;
+ }
+ for (unsigned int i = 0; i < family->keys_len; i++)
+ TALER_token_issue_pub_free (&family->keys[i].pub);
+ GNUNET_free (family->keys);
+
+ switch (family->kind)
+ {
+ case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
+ break;
+ case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
+ for (unsigned int i = 0; i < family->details.discount.expected_domains_len;
+ i++)
+ GNUNET_free (family->details.discount.expected_domains[i]);
+ break;
+ case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
+ for (unsigned int i = 0; i < family->details.subscription.
+ trusted_domains_len; i++)
+ GNUNET_free (family->details.subscription.trusted_domains[i]);
+ break;
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MERCHANT_find_token_family_key (
+ const char *slug,
+ struct GNUNET_TIME_Timestamp valid_after,
+ const struct TALER_MERCHANT_ContractTokenFamily *families,
+ unsigned int families_len,
+ struct TALER_MERCHANT_ContractTokenFamily *family,
+ struct TALER_MERCHANT_ContractTokenFamilyKey *key)
+{
+ for (unsigned int i = 0; i < families_len; i++)
+ {
+ const struct TALER_MERCHANT_ContractTokenFamily *fami
+ = &families[i];
+
+ if (0 != strcmp (fami->slug,
+ slug))
+ continue;
+ if (NULL != family)
+ *family = *fami;
+ for (unsigned int k = 0; k < fami->keys_len; k++)
+ {
+ struct TALER_MERCHANT_ContractTokenFamilyKey *ki = &fami->keys[k];
+
+ if (GNUNET_TIME_timestamp_cmp (ki->valid_after,
+ ==,
+ valid_after))
+ {
+ if (NULL != key)
+ *key = *ki;
+ return GNUNET_OK;
+ }
+ }
+ /* matching family found, but no key. */
+ return GNUNET_NO;
+ }
+
+ /* no matching family found */
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Parse token details of the given JSON contract token family.
+ *
+ * @param cls closure, unused parameter
+ * @param root the JSON object representing data
+ * @param[out] ospec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_token_details (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *ospec)
+{
+ struct TALER_MERCHANT_ContractTokenFamily *family = ospec->ptr;
+ const char *class;
+ const char *ename;
+ unsigned int eline;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("class",
+ &class),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) cls;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &ename,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse %s at %u: %s\n",
+ ispec[eline].field,
+ eline,
+ ename);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ family->kind = TALER_MERCHANT_contract_token_kind_from_string (class);
+
+ switch (family->kind)
+ {
+ case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
+ break;
+ case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
+ {
+ const json_t *trusted_domains;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("trusted_domains",
+ &trusted_domains),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ 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;
+ }
+
+ GNUNET_array_grow (family->details.subscription.trusted_domains,
+ family->details.subscription.trusted_domains_len,
+ json_array_size (trusted_domains));
+
+ for (unsigned int i = 0;
+ i < family->details.subscription.trusted_domains_len;
+ i++)
+ {
+ const json_t *jdomain;
+ jdomain = json_array_get (trusted_domains, i);
+
+ if (! json_is_string (jdomain))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ family->details.subscription.trusted_domains[i] =
+ GNUNET_strdup (json_string_value (jdomain));
+ }
+
+ return GNUNET_OK;
+ }
+ case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
+ {
+ const json_t *expected_domains;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("expected_domains",
+ &expected_domains),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (root,
+ 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;
+ }
+
+ GNUNET_array_grow (family->details.discount.expected_domains,
+ family->details.discount.expected_domains_len,
+ json_array_size (expected_domains));
+
+ for (unsigned int i = 0;
+ i < family->details.discount.expected_domains_len;
+ i++)
+ {
+ const json_t *jdomain;
+
+ jdomain = json_array_get (expected_domains,
+ i);
+ if (! json_is_string (jdomain))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ family->details.discount.expected_domains[i] =
+ GNUNET_strdup (json_string_value (jdomain));
+ }
+
+ return GNUNET_OK;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'type' invalid in token family\n");
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Provide specification to parse given JSON object to contract token details
+ * in the contract token family. All fields from @a family are copied.
+ *
+ * @param name name of the token details field in the JSON
+ * @param[out] family token_family where the token details have to be written
+ */
+static struct GNUNET_JSON_Specification
+spec_token_details (const char *name,
+ struct TALER_MERCHANT_ContractTokenFamily *family)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_token_details,
+ .field = name,
+ .ptr = family,
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to token families array.
+ *
+ * @param cls closure, pointer to array length
+ * @param root the json object representing the token families. The keys are
+ * the token family slugs.
+ * @param[out] ospec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_token_families (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *ospec)
+{
+ struct TALER_MERCHANT_ContractTokenFamily **families = ospec->ptr;
+ unsigned int *families_len = cls;
+ json_t *jfamily;
+ const char *slug;
+
+ if (! json_is_object (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ json_object_foreach (root, slug, jfamily)
+ {
+ const json_t *keys;
+ struct TALER_MERCHANT_ContractTokenFamily family = {
+ .slug = GNUNET_strdup (slug)
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string_copy ("name",
+ &family.name),
+ GNUNET_JSON_spec_string_copy ("description",
+ &family.description),
+ GNUNET_JSON_spec_object_copy ("description_i18n",
+ &family.description_i18n),
+ GNUNET_JSON_spec_array_const ("keys",
+ &keys),
+ spec_token_details ("details",
+ &family),
+ GNUNET_JSON_spec_bool ("critical",
+ &family.critical),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *error_name;
+ unsigned int error_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jfamily,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[error_line].field,
+ error_line,
+ error_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (family.keys,
+ family.keys_len,
+ json_array_size (keys));
+
+ for (unsigned int i = 0; i<family.keys_len; i++)
+ {
+ struct TALER_MERCHANT_ContractTokenFamilyKey *key = &family.keys[i];
+ struct GNUNET_JSON_Specification key_spec[] = {
+ TALER_JSON_spec_token_pub (
+ NULL,
+ &key->pub),
+ GNUNET_JSON_spec_timestamp (
+ "signature_validity_start",
+ &key->valid_after),
+ GNUNET_JSON_spec_timestamp (
+ "signature_validity_end",
+ &key->valid_before),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (keys,
+ i),
+ key_spec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ key_spec[ierror_line].field,
+ ierror_line,
+ ierror_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ GNUNET_array_append (*families,
+ *families_len,
+ family);
+ }
+
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_MERCHANT_spec_token_families (
+ const char *name,
+ struct TALER_MERCHANT_ContractTokenFamily **families,
+ unsigned int *families_len)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .cls = (void *) families_len,
+ .parser = &parse_token_families,
+ .field = name,
+ .ptr = families,
+ };
+
+ return ret;
+}
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));
+}
diff --git a/src/util/util.c b/src/util/util.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ (C) 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/util.c
+ * @brief small helpers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <string.h>
+#include "taler/taler_merchant_util.h"
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+
+
+bool
+TALER_MERCHANT_taxes_array_valid (const json_t *taxes)
+{
+ json_t *tax;
+ size_t idx;
+
+ if (! json_is_array (taxes))
+ return false;
+ json_array_foreach (taxes, idx, tax)
+ {
+ struct TALER_Amount amount;
+ const char *name;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ TALER_JSON_spec_amount_any ("tax",
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *ename;
+ unsigned int eline;
+
+ res = GNUNET_JSON_parse (tax,
+ spec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+enum TALER_MERCHANT_ContractTokenKind
+TALER_MERCHANT_contract_token_kind_from_string (const char *str)
+{
+ if (0 == strcmp ("subscription",
+ str))
+ return TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION;
+ if (0 == strcmp ("discount",
+ str))
+ return TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT;
+ return TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID;
+}