merchant

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

commit 6c3adf08b475542975ee3be2802b2429ea50fa35
parent 015c8f8ffe1b3357982c3f8af22231c2b88f2269
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 23 May 2026 22:09:04 +0200

more work on order/contract confusion

Diffstat:
Msrc/backend/taler-merchant-httpd_get-orders-ORDER_ID.c | 150++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/backend/taler-merchant-httpd_get-private-orders-ORDER_ID.c | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/backend/taler-merchant-httpd_get-private-orders.c | 88++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c | 132++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/backend/taler-merchant-httpd_post-private-orders.c | 42++++++++++++++++++++++++------------------
Msrc/backend/taler-merchant-httpd_post-templates-TEMPLATE_ID.c | 5++---
Msrc/include/taler/taler_merchant_util.h | 351+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/util/contract_parse.c | 306+++++++++----------------------------------------------------------------------
Msrc/util/contract_serialize.c | 144+++++++++++++++++++++++++------------------------------------------------------
Msrc/util/merchant_parse.c | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/util/meson.build | 9++++++++-
Msrc/util/order_parse.c | 317+++++++------------------------------------------------------------------------
Msrc/util/product_parse.c | 3++-
Msrc/util/template_parse.c | 2+-
Msrc/util/token_family_parse.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/util/util.c | 2++
16 files changed, 760 insertions(+), 1053 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,21 @@ struct GetOrderData struct TALER_MERCHANT_Contract *contract_terms; /** + * 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_CommonTerms *ct; + + /** * Total refunds granted for this payment. Only initialized * if @e refunded is set to true. */ @@ -237,6 +252,11 @@ struct GetOrderData bool contract_parsed; /** + * Did we parse the order? + */ + bool order_parsed; + + /** * Set to true if the refunds found in the DB have * a different currency then the main contract. */ @@ -312,11 +332,23 @@ suspend_god (struct GetOrderData *god) 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; } + 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; @@ -351,6 +383,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 +628,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 +733,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 +778,42 @@ 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; + 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->contract_parsed = true; + god->ct = &god->contract_terms->common; } - 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->common; + } + GNUNET_assert ( (NULL != god->order) || + (NULL != god->contract_terms) ); + 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 +828,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 +884,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 +934,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 +950,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 +971,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 +1027,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 +1063,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 +1081,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 +1147,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 +1204,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 +1220,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,7 +1371,8 @@ 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; @@ -1483,8 +1551,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 +1569,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->ct->merchant_base_url, god->order_id); if (NULL == uri) { @@ -1565,7 +1633,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,22 @@ struct GetOrderRequestContext struct TALER_MERCHANT_Contract *contract_terms; /** + * 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_CommonTerms *ct; + + /** * Claim token of the order. */ struct TALER_ClaimTokenP claim_token; @@ -515,12 +531,26 @@ 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; } + 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 +625,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 +639,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,11 +773,30 @@ 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->order = TALER_MERCHANT_order_parse ( + gorc->order_json); + + if (NULL == gorc->order) + { + 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->order->common; + } + if ( (NULL == gorc->contract_terms) && + (NULL != gorc->contract_terms_json) ) { gorc->contract_terms = TALER_MERCHANT_contract_parse ( - gorc->contract_terms_json, - true); + gorc->contract_terms_json); if (NULL == gorc->contract_terms) { @@ -760,12 +809,16 @@ phase_parse_contract (struct GetOrderRequestContext *gorc) hc->infix)); return; } + gorc->ct = &gorc->contract_terms->common; } - 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->contract_terms) + ? gorc->contract_terms->details.v0.brutto + : gorc->order->details.v0.brutto; break; case TALER_MERCHANT_CONTRACT_VERSION_1: if (gorc->choice_index >= 0) @@ -782,9 +835,10 @@ phase_parse_contract (struct GetOrderRequestContext *gorc) NULL)); return; } - gorc->contract_amount = - gorc->contract_terms->details.v1.choices[gorc->choice_index].amount; + (NULL != gorc->contract_terms) + ? gorc->contract_terms->details.v1.choices[gorc->choice_index].amount + : gorc->order->details.v1.choices[gorc->choice_index].amount; } else { @@ -792,7 +846,7 @@ phase_parse_contract (struct GetOrderRequestContext *gorc) 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); + gorc->ct->order_id); } break; default: @@ -808,7 +862,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 +875,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->contract_terms) || + (NULL != gorc->order) ); gorc->phase++; } @@ -982,7 +1038,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 +1048,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 +1074,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 +1118,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 +1128,11 @@ 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), + gorc->ct->pay_deadline), GNUNET_JSON_pack_timestamp ("creation_time", - gorc->contract_terms->timestamp)); + gorc->ct->timestamp)); GNUNET_free (order_status_url); GNUNET_free (taler_pay_uri); @@ -1168,9 +1224,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->ct->timestamp)); check_reply (gorc, reply); json_decref (reply); @@ -1584,7 +1640,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->ct->timestamp, gorc->order_serial); } } @@ -1630,7 +1686,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->ct->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_CommonTerms *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->common; + } } 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->common; + } } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { @@ -560,20 +583,13 @@ 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; @@ -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->common.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->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]; + GNUNET_assert (choice_index < contract->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->common.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->common.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->common.h_wire, + sizeof (eg->pc->check_contract.contract_terms->common.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->common.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->common.timestamp, + .merchant_pub + = hc->instance->merchant_pub, + .refund_deadline + = pc->check_contract.contract_terms->common.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->common.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->common.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->common.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->common.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->common.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->common.fulfillment_url, + strlen (pc->check_contract.contract_terms->common.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->common.products_len; i++) { const struct TALER_MERCHANT_ProductSold *product - = &contract->products[i]; + = &contract->common.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->common.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->common.default_money_pot, TALER_amount2s (&left)); increment_pot (pc, - contract->default_money_pot, + contract->common.default_money_pot, &left); } } @@ -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->common.timestamp, + pc->check_contract.order_serial); { enum GNUNET_DB_QueryStatus qs; json_t *jhook; @@ -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->common.timestamp, + pc->check_contract.contract_terms->common.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->common.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->common.pay_deadline.abs_time.abs_value_us ); GNUNET_break (0); pay_end (pc, @@ -3837,7 +3846,7 @@ 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->common.version) { case TALER_MERCHANT_CONTRACT_VERSION_0: /* No tokens to validate */ @@ -4175,7 +4184,7 @@ append_output_token_sig (void *cls, 0, sizeof (out)); GNUNET_assert (TALER_MERCHANT_CONTRACT_VERSION_1 == - pc->check_contract.contract_terms->version); + pc->check_contract.contract_terms->common.version); choice = &pc->check_contract.contract_terms->details.v1 .choices[pc->parse_wallet_data.choice_index]; output = &choice->outputs[pc->output_index_gen]; @@ -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->common.version) { case TALER_MERCHANT_CONTRACT_VERSION_0: { @@ -4530,11 +4539,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->common.wire_deadline, + <, + pc->check_contract.contract_terms->common.refund_deadline)) { /* This should already have been checked when creating the order! */ GNUNET_break (0); @@ -4546,8 +4554,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->common.pay_deadline.abs_time)) { /* too late */ pay_end (pc, @@ -4564,8 +4572,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->common.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 @@ -493,7 +493,7 @@ struct OrderContext * from by selecting the respective index when signing the deposit * confirmation. */ - struct TALER_MERCHANT_ContractChoice *choices; + struct TALER_MERCHANT_OrderChoice *choices; /** * Length of the @e choices array. @@ -972,7 +972,7 @@ clean_order (void *cls) } for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) { - TALER_MERCHANT_contract_choice_free (&oc->parse_choices.choices[i]); + TALER_MERCHANT_order_choice_free (&oc->parse_choices.choices[i]); } GNUNET_array_grow (oc->parse_choices.choices, oc->parse_choices.choices_len, @@ -2223,11 +2223,9 @@ output_contract_choices (struct OrderContext *oc) oc->set_max_fee.details.v1.max_fees[i]; GNUNET_assert (0 == json_array_append_new ( choices, - TALER_MERCHANT_json_from_contract_choice ( - &oc->parse_choices.choices[i], - false))); + TALER_MERCHANT_json_from_order_choice ( + &oc->parse_choices.choices[i]))); } - return choices; } @@ -3627,7 +3625,7 @@ add_donau_url (void *cls, */ static bool add_donau_output (struct OrderContext *oc, - struct TALER_MERCHANT_ContractOutput *output) + struct TALER_MERCHANT_OrderOutput *output) { enum GNUNET_DB_QueryStatus qs; @@ -3696,7 +3694,7 @@ phase_parse_choices (struct OrderContext *oc) json_array_size (jchoices)); for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) { - struct TALER_MERCHANT_ContractChoice *choice + struct TALER_MERCHANT_OrderChoice *choice = &oc->parse_choices.choices[i]; const char *error_name; unsigned int error_line; @@ -3797,15 +3795,15 @@ phase_parse_choices (struct OrderContext *oc) size_t idx; json_array_foreach ((json_t *) jinputs, idx, jinput) { - struct TALER_MERCHANT_ContractInput input = { + struct TALER_MERCHANT_OrderInput input = { .details.token.count = 1 }; if (GNUNET_OK != - TALER_MERCHANT_parse_choice_input ((json_t *) jinput, - &input, - idx, - true)) + TALER_MERCHANT_parse_order_choice_input ( + (json_t *) jinput, + &input, + idx)) { GNUNET_break_op (0); reply_with_error (oc, @@ -3853,15 +3851,15 @@ phase_parse_choices (struct OrderContext *oc) size_t idx; json_array_foreach ((json_t *) joutputs, idx, joutput) { - struct TALER_MERCHANT_ContractOutput output = { + struct TALER_MERCHANT_OrderOutput output = { .details.token.count = 1 }; if (GNUNET_OK != - TALER_MERCHANT_parse_choice_output ((json_t *) joutput, - &output, - idx, - true)) + TALER_MERCHANT_parse_order_choice_output ( + (json_t *) joutput, + &output, + idx)) { reply_with_error (oc, MHD_HTTP_BAD_REQUEST, @@ -3877,6 +3875,8 @@ phase_parse_choices (struct OrderContext *oc) break; case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: output.details.donation_receipt.amount = choice->amount; + // FIXME: This is also already a transformation, + // not reflected in the "Order" type! if (! add_donau_output (oc, &output)) { @@ -3895,6 +3895,12 @@ phase_parse_choices (struct OrderContext *oc) if (0 == output.details.token.valid_at.abs_time.abs_value_us) output.details.token.valid_at = GNUNET_TIME_timestamp_get (); + // FIXME: This is another transformation, + // not reflected in the "Order" type! + // => need yet another type for the data that is + // actually POSTed to this endpoint! + // (Note: the 'metadata' is another one that + // is currently *wrong*!) if (GNUNET_OK != add_output_token_family (oc, output.details.token.token_family_slug, 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 @@ -1337,10 +1337,21 @@ struct TALER_MERCHANT_MetaData /** - * Struct to hold contract terms. + * Struct to hold terms common to both orders and contracts. */ -struct TALER_MERCHANT_Contract +struct TALER_MERCHANT_CommonTerms { + + /** + * Merchant public key. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * The hash of the merchant instance's wire details. + */ + struct TALER_MerchantWireHashP h_wire; + /** * URL where the same contract could be ordered again (if available). */ @@ -1359,7 +1370,7 @@ struct TALER_MERCHANT_Contract /** * Metadata about the merchant. */ - struct TALER_MERCHANT_Metadata merchant; + struct TALER_MERCHANT_MetaData merchant; /** * Summary of the contract. @@ -1389,9 +1400,26 @@ struct TALER_MERCHANT_Contract json_t *fulfillment_message_i18n; /** - * Length of the @e products array. + * Wire transfer method identifier for the wire method associated with + * @e h_wire. */ - size_t products_len; + char *wire_method; + + /** + * Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + * TODO: appropriate type + */ + json_t *exchanges; + + /** + * Delivery location. + */ + json_t *delivery_location; + + /** + * Extra data that is only interpreted by the merchant frontend. + */ + json_t *extra; /** * Array of products that are part of the purchase. @@ -1399,6 +1427,11 @@ struct TALER_MERCHANT_Contract struct TALER_MERCHANT_ProductSold *products; /** + * Length of the @e products array. + */ + size_t products_len; + + /** * Timestamp of the contract. */ struct GNUNET_TIME_Timestamp timestamp; @@ -1437,31 +1470,36 @@ struct TALER_MERCHANT_Contract struct GNUNET_TIME_Timestamp max_pickup_time; /** - * Merchant public key. + * Default money pot to use for this product, applies to the + * amount remaining that was not claimed by money pots of + * products or taxes. Not useful to wallets, only for + * merchant-internal accounting. If zero, the remaining + * account is simply not accounted for in any money pot. */ - struct TALER_MerchantPublicKeyP merchant_pub; + uint64_t default_money_pot; /** - * The hash of the merchant instance's wire details. + * Specified version of the contract. */ - struct TALER_MerchantWireHashP h_wire; + enum TALER_MERCHANT_ContractVersion version; /** - * Wire transfer method identifier for the wire method associated with - * @e h_wire. + * Minimum age the buyer must have (in years). */ - char *wire_method; + uint8_t minimum_age; - /** - * Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - * TODO: appropriate type - */ - json_t *exchanges; +}; + +/** + * Struct to hold contract terms. + */ +struct TALER_MERCHANT_Contract +{ /** - * Delivery location. + * Terms common with contracts and orders. */ - json_t *delivery_location; + struct TALER_MERCHANT_CommonTerms common; /** * Nonce generated by the wallet and echoed by the merchant @@ -1470,30 +1508,6 @@ struct TALER_MERCHANT_Contract char *nonce; /** - * 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 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. - */ - uint64_t default_money_pot; - - /** - * Specified version of the contract. - */ - enum TALER_MERCHANT_ContractVersion version; - - /** * Details depending on the @e version. */ union @@ -1570,151 +1584,11 @@ struct TALER_MERCHANT_Contract */ struct TALER_MERCHANT_Order { - /** - * URL where the same order could be ordered again (if available). - */ - char *public_reorder_url; - - /** - * Our order ID. - */ - char *order_id; - - /** - * Merchant base URL. - */ - char *merchant_base_url; - - /** - * Metadata about the merchant. - */ - struct TALER_MERCHANT_Metadata merchant; - - /** - * Summary of the contract. - */ - char *summary; - - /** - * Internationalized summary. - */ - json_t *summary_i18n; - - /** - * URL that will show that the contract was successful - * after it has been paid for. - */ - char *fulfillment_url; - - /** - * Message shown to the customer after paying for the contract. - * Either fulfillment_url or fulfillment_message must be specified. - */ - char *fulfillment_message; - - /** - * Map from IETF BCP 47 language tags to localized fulfillment messages. - */ - json_t *fulfillment_message_i18n; /** - * Length of the @e products array. - */ - size_t products_len; - - /** - * Array of products that are part of the purchase. - */ - struct TALER_MERCHANT_ProductSold *products; - - /** - * Timestamp of the contract. - */ - struct GNUNET_TIME_Timestamp timestamp; - - /** - * Deadline for refunds. - */ - 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. - */ - struct GNUNET_TIME_Timestamp pay_deadline; - - /** - * Wire transfer deadline. - */ - struct GNUNET_TIME_Timestamp wire_deadline; - - /** - * Delivery date. - */ - struct GNUNET_TIME_Timestamp delivery_date; - - /** - * 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. - */ - struct GNUNET_TIME_Timestamp max_pickup_time; - - /** - * Merchant public key. - */ - struct TALER_MerchantPublicKeyP merchant_pub; - - /** - * The hash of the merchant instance's wire details. - */ - struct TALER_MerchantWireHashP h_wire; - - /** - * Wire transfer method identifier for the wire method associated with - * @e h_wire. - */ - char *wire_method; - - /** - * Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - * TODO: appropriate type - */ - json_t *exchanges; - - /** - * Delivery location. - */ - json_t *delivery_location; - - /** - * Extra data that is only interpreted by the merchant frontend. - */ - json_t *extra; - - /** - * Minimum age the buyer must have (in years). + * Terms common with contracts and orders. */ - uint8_t minimum_age; - - /** - * 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. - */ - uint64_t default_money_pot; - - /** - * Specified version of the contract. - */ - enum TALER_MERCHANT_ContractVersion version; + struct TALER_MERCHANT_CommonTerms common; /** * Details depending on the @e version. @@ -1789,13 +1663,49 @@ struct TALER_MERCHANT_Order /** + * Parse common terms (of orders and contracts) in @a input to + * initialize @a ct. + * + * @param[out] ct common terms to initialize + * @param[in] input JSON object containing contract terms + * @return true on success, false if @a input is malformed + */ +bool +TALER_MERCHANT_common_terms_parse ( + struct TALER_MERCHANT_CommonTerms *ct, + json_t *input); + + +/** + * Release memory in @a ct, but not @a ct itself. + * + * @param[in,out] ct common terms to clean up + */ +void +TALER_MERCHANT_common_terms_free ( + struct TALER_MERCHANT_CommonTerms *ct); + + +/** + * Get JSON representation of merchant details. + * + * @param[in] merchant metadata to serialize + * @return JSON object with merchant details; NULL on error + */ +json_t * +TALER_MERCHANT_metadata_to_json ( + const struct TALER_MERCHANT_MetaData *merchant); + + +/** * 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); +TALER_MERCHANT_contract_parse ( + json_t *input); /** @@ -1805,7 +1715,8 @@ TALER_MERCHANT_contract_parse (json_t *input); * @return parsed contract terms; NULL if @a input is malformed */ struct TALER_MERCHANT_Order * -TALER_MERCHANT_order_parse (json_t *input); +TALER_MERCHANT_order_parse ( + json_t *input); /** @@ -1817,8 +1728,9 @@ TALER_MERCHANT_order_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); /** @@ -1836,6 +1748,19 @@ TALER_MERCHANT_spec_contract_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); + + +/** * Provide specification to parse given JSON array to order * choices. All fields from @a choices elements are copied. * @@ -1866,6 +1791,21 @@ TALER_MERCHANT_spec_contract_choices ( /** + * 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 @@ -1904,7 +1844,8 @@ TALER_MERCHANT_parse_order_choice_input ( * entries are valid Taxes. */ bool -TALER_MERCHANT_taxes_array_valid (const json_t *taxes); +TALER_MERCHANT_taxes_array_valid ( + const json_t *taxes); /** @@ -1916,8 +1857,9 @@ TALER_MERCHANT_taxes_array_valid (const json_t *taxes); * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_product_sold (const json_t *p, - struct TALER_MERCHANT_ProductSold *r); +TALER_MERCHANT_parse_product_sold ( + const json_t *p, + struct TALER_MERCHANT_ProductSold *r); /** @@ -1929,8 +1871,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); /** @@ -2075,12 +2018,24 @@ TALER_MERCHANT_order_choice_free ( /** + * 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 + */ +void +TALER_MERCHANT_contract_token_family_free ( + struct TALER_MERCHANT_ContractTokenFamily *family); + + +/** * Release memory inside of @a product, but not @a product itself. * * @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); /** @@ -2089,7 +2044,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); /** @@ -2098,7 +2054,8 @@ TALER_MERCHANT_contract_free (struct TALER_MERCHANT_Contract *contract); * @param[in] order order to free */ void -TALER_MERCHANT_order_free (struct TALER_MERCHANT_Order *order); +TALER_MERCHANT_order_free ( + struct TALER_MERCHANT_Order *order); #endif diff --git a/src/util/contract_parse.c b/src/util/contract_parse.c @@ -31,11 +31,10 @@ 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) { const char *ename; unsigned int eline; @@ -105,11 +104,10 @@ TALER_MERCHANT_parse_choice_input ( 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) { const char *ename; unsigned int eline; @@ -152,10 +150,8 @@ TALER_MERCHANT_parse_choice_output ( 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_uint ("key_index", + &output->details.token.key_index), GNUNET_JSON_spec_end () }; @@ -184,10 +180,8 @@ TALER_MERCHANT_parse_choice_output ( 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_array_const ("donau_urls", + &donau_urls), GNUNET_JSON_spec_end () }; @@ -248,7 +242,7 @@ TALER_MERCHANT_parse_choice_output ( * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error */ static enum GNUNET_GenericReturnValue -parse_choices ( +parse_contract_choices ( void *cls, json_t *root, struct GNUNET_JSON_Specification *ospec) @@ -348,10 +342,9 @@ parse_choices ( }; if (GNUNET_OK != - TALER_MERCHANT_parse_choice_input ((json_t *) jinput, - &input, - idx, - false)) + TALER_MERCHANT_parse_contract_choice_input ((json_t *) jinput, + &input, + idx)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -384,10 +377,9 @@ parse_choices ( }; if (GNUNET_OK != - TALER_MERCHANT_parse_choice_output ((json_t *) joutput, - &output, - idx, - false)) + TALER_MERCHANT_parse_contract_choice_output ((json_t *) joutput, + &output, + idx)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -417,14 +409,14 @@ parse_choices ( 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) { struct GNUNET_JSON_Specification ret = { .cls = (void *) choices_len, - .parser = &parse_choices, + .parser = &parse_contract_choices, .field = name, .ptr = choices, }; @@ -475,87 +467,6 @@ TALER_MERCHANT_contract_choice_free ( } -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 v0-specific fields of @a input JSON into @a contract. * @@ -633,7 +544,7 @@ parse_contract_v1 ( struct TALER_MERCHANT_Contract *contract) { struct GNUNET_JSON_Specification espec[] = { - TALER_MERCHANT_spec_choices ( + TALER_MERCHANT_spec_contract_choices ( "choices", &contract->details.v1.choices, &contract->details.v1.choices_len), @@ -666,108 +577,27 @@ parse_contract_v1 ( 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[] = { - TALER_MERCHANT_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), - TALER_MERCHANT_spec_merchant_details ("merchant", - &contract->merchant), - 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; + if (! TALER_MERCHANT_common_terms_parse (&contract->common, + input)) + { + GNUNET_break (0); + GNUNET_free (contract); + return NULL; + } res = GNUNET_JSON_parse (input, espec, &ename, @@ -780,32 +610,7 @@ TALER_MERCHANT_contract_parse (json_t *input, ename); goto cleanup; } - if (NULL != products) - { - contract->products_len = json_array_size (products); - if (0 != contract->products_len) - { - size_t i; - json_t *p; - - contract->products = GNUNET_new_array (contract->products_len, - struct TALER_MERCHANT_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) + switch (contract->common.version) { case TALER_MERCHANT_CONTRACT_VERSION_0: res = parse_contract_v0 (input, @@ -838,56 +643,8 @@ TALER_MERCHANT_contract_free ( { 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) - { - json_decref (contract->delivery_location); - contract->delivery_location = NULL; - } GNUNET_free (contract->nonce); - if (NULL != contract->extra) - { - json_decref (contract->extra); - contract->extra = NULL; - } - - switch (contract->version) + switch (contract->common.version) { case TALER_MERCHANT_CONTRACT_VERSION_0: break; @@ -900,9 +657,10 @@ TALER_MERCHANT_contract_free ( for (unsigned int i = 0; i < contract->details.v1.token_authorities_len; i++) - contract_token_family_free (&contract->details.v1.token_authorities[i]); + TALER_MERCHANT_contract_token_family_free (&contract->details.v1.token_authorities[i]); GNUNET_free (contract->details.v1.token_authorities); break; } + TALER_MERCHANT_common_terms_free (&contract->common); 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,36 +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. @@ -156,8 +125,7 @@ json_from_contract_output ( json_t * TALER_MERCHANT_json_from_contract_choice ( - const struct TALER_MERCHANT_ContractChoice *choice, - bool order) + const struct TALER_MERCHANT_ContractChoice *choice) { json_t *inputs; json_t *outputs; @@ -191,29 +159,12 @@ TALER_MERCHANT_json_from_contract_choice ( 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)); + TALER_JSON_pack_amount ("max_fee", + &choice->max_fee), + GNUNET_JSON_pack_array_steal ("inputs", + inputs), + GNUNET_JSON_pack_array_steal ("outputs", + outputs)); } @@ -375,8 +326,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); @@ -485,13 +435,13 @@ TALER_MERCHANT_product_sold_serialize ( json_t * TALER_MERCHANT_contract_serialize ( - const struct TALER_MERCHANT_Contract *input, - bool nonce_optional) + const struct TALER_MERCHANT_Contract *input) { + struct TALER_MERCHANT_CommonTerms *ct = &input->common; json_t *details; json_t *products; - switch (input->version) + switch (ct->version) { case TALER_MERCHANT_CONTRACT_VERSION_0: details = json_from_contract_v0 (input); @@ -503,99 +453,97 @@ TALER_MERCHANT_contract_serialize ( GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "unknown contract type version %d", - input->version); + ct->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<ct->products_len; i++) { GNUNET_assert ( 0 == json_array_append_new (products, TALER_MERCHANT_product_sold_serialize ( - &input->products[i]))); + &ct->products[i]))); } return GNUNET_JSON_PACK ( GNUNET_JSON_pack_uint64 ("version", - input->version), + ct->version), GNUNET_JSON_pack_string ("summary", - input->summary), + ct->summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_steal ("summary_i18n", - input->summary_i18n)), + ct->summary_i18n)), GNUNET_JSON_pack_string ("order_id", - input->order_id), + ct->order_id), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("public_reorder_url", - input->public_reorder_url)), + ct->public_reorder_url)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", - input->fulfillment_url)), + ct->fulfillment_url)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_message", - input->fulfillment_message)), + ct->fulfillment_message)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n", - input->fulfillment_message_i18n)), + ct->fulfillment_message_i18n)), GNUNET_JSON_pack_array_steal ("products", products), GNUNET_JSON_pack_timestamp ("timestamp", - input->timestamp), + ct->timestamp), GNUNET_JSON_pack_timestamp ("refund_deadline", - input->refund_deadline), + ct->refund_deadline), GNUNET_JSON_pack_timestamp ("pay_deadline", - input->pay_deadline), + ct->pay_deadline), GNUNET_JSON_pack_timestamp ("wire_transfer_deadline", - input->wire_deadline), + ct->wire_deadline), GNUNET_JSON_pack_data_auto ("merchant_pub", - &input->merchant_pub.eddsa_pub), + &ct->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)), + ct->merchant_base_url), + GNUNET_JSON_pack_object_steal ( + "merchant", + TALER_MERCHANT_metadata_to_json ( + &input->common.merchant)), GNUNET_JSON_pack_data_auto ("h_wire", - &input->h_wire), + &ct->h_wire), GNUNET_JSON_pack_string ("wire_method", - input->wire_method), + ct->wire_method), GNUNET_JSON_pack_array_steal ("exchanges", - input->exchanges), + ct->exchanges), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_steal ("delivery_location", - input->delivery_location)), + ct->delivery_location)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_timestamp ("delivery_date", - input->delivery_date)), + ct->delivery_date)), GNUNET_JSON_pack_allow_null ( - GNUNET_TIME_absolute_is_never (input->max_pickup_time.abs_time) + GNUNET_TIME_absolute_is_never (ct->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), + ct->max_pickup_time)), + GNUNET_JSON_pack_string ("nonce", + input->nonce), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_time_rel ("auto_refund", - input->auto_refund)), + ct->auto_refund)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_steal ("extra", - input->extra)), + ct->extra)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_uint64 ("minimum_age", - input->minimum_age)), - (0 == input->default_money_pot) + ct->minimum_age)), + (0 == ct->default_money_pot) ? GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("dummy", NULL)) : GNUNET_JSON_pack_uint64 ("default_money_pot", - input->default_money_pot), + ct->default_money_pot), GNUNET_JSON_pack_object_steal (NULL, details)); } diff --git a/src/util/merchant_parse.c b/src/util/merchant_parse.c @@ -1,3 +1,35 @@ +/* + 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. * @@ -59,16 +91,35 @@ parse_merchant_details (void *cls, } -/** - * 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 - */ +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) +TALER_MERCHANT_spec_merchant_details ( + const char *name, + struct TALER_MERCHANT_MetaData *merchant) { struct GNUNET_JSON_Specification ret = { .parser = &parse_merchant_details, diff --git a/src/util/meson.build b/src/util/meson.build @@ -15,14 +15,21 @@ endforeach libtalermerchantutil_SOURCES = [ 'amount_quantity.c', + 'common_terms_parse.c', 'contract_parse.c', 'contract_serialize.c', + 'contract_version_parse.c', 'json.c', + 'merchant_parse.c', 'mfa.c', + 'order_parse.c', 'os_installation.c', + 'product_parse.c', 'template_parse.c', - 'value_kinds.c', + 'token_family_parse.c', + 'util.c', 'validators.c', + 'value_kinds.c', ] libtalermerchantutil = library( diff --git a/src/util/order_parse.c b/src/util/order_parse.c @@ -31,11 +31,10 @@ enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_choice_input ( +TALER_MERCHANT_parse_order_choice_input ( json_t *root, struct TALER_MERCHANT_OrderInput *input, - size_t index, - bool order) + size_t index) { const char *ename; unsigned int eline; @@ -105,11 +104,10 @@ TALER_MERCHANT_parse_choice_input ( enum GNUNET_GenericReturnValue -TALER_MERCHANT_parse_choice_output ( +TALER_MERCHANT_parse_order_choice_output ( json_t *root, struct TALER_MERCHANT_OrderOutput *output, - size_t index, - bool order) + size_t index) { const char *ename; unsigned int eline; @@ -152,10 +150,6 @@ TALER_MERCHANT_parse_choice_output ( 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 () }; @@ -184,10 +178,6 @@ TALER_MERCHANT_parse_choice_output ( 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 () }; @@ -248,7 +238,7 @@ TALER_MERCHANT_parse_choice_output ( * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error */ static enum GNUNET_GenericReturnValue -parse_choices ( +parse_order_choices ( void *cls, json_t *root, struct GNUNET_JSON_Specification *ospec) @@ -348,10 +338,9 @@ parse_choices ( }; if (GNUNET_OK != - TALER_MERCHANT_parse_choice_input ((json_t *) jinput, - &input, - idx, - false)) + TALER_MERCHANT_parse_order_choice_input ((json_t *) jinput, + &input, + idx)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -384,10 +373,9 @@ parse_choices ( }; if (GNUNET_OK != - TALER_MERCHANT_parse_choice_output ((json_t *) joutput, - &output, - idx, - false)) + TALER_MERCHANT_parse_order_choice_output ((json_t *) joutput, + &output, + idx)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -417,14 +405,14 @@ parse_choices ( struct GNUNET_JSON_Specification -TALER_MERCHANT_spec_choices ( +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_choices, + .parser = &parse_order_choices, .field = name, .ptr = choices, }; @@ -475,87 +463,6 @@ TALER_MERCHANT_order_choice_free ( } -enum GNUNET_GenericReturnValue -TALER_MERCHANT_find_token_family_key ( - const char *slug, - struct GNUNET_TIME_Timestamp valid_after, - const struct TALER_MERCHANT_OrderTokenFamily *families, - unsigned int families_len, - struct TALER_MERCHANT_OrderTokenFamily *family, - struct TALER_MERCHANT_OrderTokenFamilyKey *key) -{ - for (unsigned int i = 0; i < families_len; i++) - { - const struct TALER_MERCHANT_OrderTokenFamily *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_OrderTokenFamilyKey *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 order token family to free - */ -static void -order_token_family_free ( - struct TALER_MERCHANT_OrderTokenFamily *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 v0-specific fields of @a input JSON into @a order. * @@ -633,11 +540,11 @@ parse_order_v1 ( struct TALER_MERCHANT_Order *order) { struct GNUNET_JSON_Specification espec[] = { - TALER_MERCHANT_spec_choices ( + TALER_MERCHANT_spec_order_choices ( "choices", &order->details.v1.choices, &order->details.v1.choices_len), - spec_token_families ( + TALER_MERCHANT_spec_token_families ( "token_families", &order->details.v1.token_authorities, &order->details.v1.token_authorities_len), @@ -666,146 +573,22 @@ parse_order_v1 ( struct TALER_MERCHANT_Order * -TALER_MERCHANT_order_parse (json_t *input, - bool nonce_optional) +TALER_MERCHANT_order_parse (json_t *input) { struct TALER_MERCHANT_Order *order = GNUNET_new (struct TALER_MERCHANT_Order); - const json_t *products = NULL; - struct GNUNET_JSON_Specification espec[] = { - TALER_MERCHANT_spec_contract_version ("version", - &order->version), - GNUNET_JSON_spec_string_copy ("summary", - &order->summary), - /* FIXME: do i18n_str validation in the future */ - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_copy ("summary_i18n", - &order->summary_i18n), - NULL), - GNUNET_JSON_spec_string_copy ("order_id", - &order->order_id), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string_copy ("public_reorder_url", - &order->public_reorder_url), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string_copy ("fulfillment_url", - &order->fulfillment_url), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string_copy ("fulfillment_message", - &order->fulfillment_message), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_copy ("fulfillment_message_i18n", - &order->fulfillment_message_i18n), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("products", - &products), - NULL), - GNUNET_JSON_spec_timestamp ("timestamp", - &order->timestamp), - GNUNET_JSON_spec_timestamp ("refund_deadline", - &order->refund_deadline), - GNUNET_JSON_spec_timestamp ("pay_deadline", - &order->pay_deadline), - GNUNET_JSON_spec_timestamp ("wire_transfer_deadline", - &order->wire_deadline), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &order->merchant_pub), - GNUNET_JSON_spec_string_copy ("merchant_base_url", - &order->merchant_base_url), - TALER_MERCHANT_spec_merchant_details ("merchant", - &order->merchant), - GNUNET_JSON_spec_fixed_auto ("h_wire", - &order->h_wire), - GNUNET_JSON_spec_string_copy ("wire_method", - &order->wire_method), - GNUNET_JSON_spec_array_copy ("exchanges", - &order->exchanges), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_copy ("delivery_location", - &order->delivery_location), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("delivery_date", - &order->delivery_date), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("max_pickup_time", - &order->max_pickup_time), - NULL), - (nonce_optional) - ? GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string_copy ("nonce", - &order->nonce), - NULL) - : GNUNET_JSON_spec_string_copy ("nonce", - &order->nonce), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_relative_time ("auto_refund", - &order->auto_refund), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_copy ("extra", - &order->extra), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint8 ("minimum_age", - &order->minimum_age), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint64 ("default_money_pot", - &order->default_money_pot), - NULL), - GNUNET_JSON_spec_end () - }; - - enum GNUNET_GenericReturnValue res; - const char *ename; - unsigned int eline; + enum GNUNET_GenericReturnValue res + = GNUNET_SYSERR; GNUNET_assert (NULL != input); - order->max_pickup_time = GNUNET_TIME_UNIT_FOREVER_TS; - res = GNUNET_JSON_parse (input, - espec, - &ename, - &eline); - if (GNUNET_OK != res) + if (! TALER_MERCHANT_common_terms_parse (&order->common, + input)) { GNUNET_break (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse order 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])) - { - GNUNET_break (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse product at offset %u\n", - (unsigned int) i); - goto cleanup; - } - } - } + GNUNET_free (order); + return NULL; } - switch (order->version) + switch (order->common.version) { case TALER_MERCHANT_CONTRACT_VERSION_0: res = parse_order_v0 (input, @@ -838,56 +621,7 @@ TALER_MERCHANT_order_free ( { if (NULL == order) return; - GNUNET_free (order->public_reorder_url); - GNUNET_free (order->order_id); - GNUNET_free (order->merchant_base_url); - GNUNET_free (order->merchant.name); - GNUNET_free (order->merchant.website); - GNUNET_free (order->merchant.email); - GNUNET_free (order->merchant.logo); - if (NULL != order->merchant.address) - { - json_decref (order->merchant.address); - order->merchant.address = NULL; - } - if (NULL != order->merchant.jurisdiction) - { - json_decref (order->merchant.jurisdiction); - order->merchant.jurisdiction = NULL; - } - GNUNET_free (order->summary); - GNUNET_free (order->fulfillment_url); - GNUNET_free (order->fulfillment_message); - if (NULL != order->fulfillment_message_i18n) - { - json_decref (order->fulfillment_message_i18n); - order->fulfillment_message_i18n = NULL; - } - 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); - } - GNUNET_free (order->wire_method); - if (NULL != order->exchanges) - { - json_decref (order->exchanges); - order->exchanges = NULL; - } - if (NULL != order->delivery_location) - { - json_decref (order->delivery_location); - order->delivery_location = NULL; - } - GNUNET_free (order->nonce); - if (NULL != order->extra) - { - json_decref (order->extra); - order->extra = NULL; - } - - switch (order->version) + switch (order->common.version) { case TALER_MERCHANT_CONTRACT_VERSION_0: break; @@ -900,9 +634,10 @@ TALER_MERCHANT_order_free ( for (unsigned int i = 0; i < order->details.v1.token_authorities_len; i++) - order_token_family_free (&order->details.v1.token_authorities[i]); + TALER_MERCHANT_contract_token_family_free (&order->details.v1.token_authorities[i]); GNUNET_free (order->details.v1.token_authorities); break; } + TALER_MERCHANT_common_terms_free (&order->common); GNUNET_free (order); } diff --git a/src/util/product_parse.c b/src/util/product_parse.c @@ -21,7 +21,8 @@ #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. diff --git a/src/util/template_parse.c b/src/util/template_parse.c @@ -348,7 +348,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/token_family_parse.c b/src/util/token_family_parse.c @@ -21,6 +21,83 @@ #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; +} /** @@ -305,14 +382,6 @@ parse_token_families (void *cls, } -/** - * 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, diff --git a/src/util/util.c b/src/util/util.c @@ -21,6 +21,8 @@ #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