diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders.c | 720 |
1 files changed, 672 insertions, 48 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 7ca56319..b2070bcc 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -22,19 +22,25 @@ * @brief the POST /orders handler * @author Christian Grothoff * @author Marcello Stanisci + * @author Christian Blättler */ #include "platform.h" #include <gnunet/gnunet_common.h> #include <gnunet/gnunet_json_lib.h> #include <gnunet/gnunet_time_lib.h> #include <jansson.h> +#include <microhttpd.h> #include <string.h> +#include <taler/taler_error_codes.h> #include <taler/taler_signatures.h> #include <taler/taler_json_lib.h> +#include "taler-merchant-httpd.h" #include "taler-merchant-httpd_private-post-orders.h" #include "taler-merchant-httpd_exchanges.h" +#include "taler-merchant-httpd_contract.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_private-get-orders.h" +#include "taler_merchantdb_plugin.h" /** @@ -231,43 +237,58 @@ struct OrderContext struct { /** + * Version of the contract terms. + */ + enum TALER_MerchantContractVersion version; + + /** * Our order ID. */ const char *order_id; /** - * Summary of the order. - */ + * Summary of the contract. + */ const char *summary; /** - * Internationalized summary. - */ + * Internationalized summary. + */ json_t *summary_i18n; /** - * URL where the same contract could be ordered again (if available). - */ - const char *public_reorder_url; - - /** - * URL that will show that the order was successful - * after it has been paid for. - */ + * 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 order. - * Either fulfillment_url or fulfillment_message must be specified. - */ + * 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. - */ + * Map from IETF BCP 47 language tags to localized fulfillment messages. + */ json_t *fulfillment_message_i18n; /** + * Array of products that are part of the purchase. + */ + const json_t *products; + + /** + * URL where the same contract could be ordered again (if available). + */ + const char *public_reorder_url; + + /** + * Array of contract choices. Is null for v0 contracts. + */ + const json_t *choices; + + /** * Merchant base URL. */ char *merchant_base_url; @@ -303,14 +324,9 @@ struct OrderContext json_t *delivery_location; /** - * Array of products that are part of the purchase. - */ - const json_t *products; - - /** - * Gross amount value of the contract. Used to - * compute @e max_stefan_fee. - */ + * Gross amount value of the contract. Used to + * compute @e max_stefan_fee. + */ struct TALER_Amount brutto; /** @@ -343,6 +359,34 @@ struct OrderContext } parse_order; /** + * Information set in the ORDER_PHASE_PARSE_CHOICES phase. + */ + struct + { + /** + * Array of possible specific contracts the wallet/customer may choose + * from by selecting the respective index when signing the deposit + * confirmation. + */ + struct TALER_MerchantContractChoice *choices; + + /** + * Length of the @e choices array. + */ + unsigned int choices_len; + + /** + * Array of token types referenced in the contract. + */ + struct TALER_MerchantContractTokenAuthority *authorities; + + /** + * Length of the @e authorities array. + */ + unsigned int authorities_len; + } parse_choices; + + /** * Information set in the ORDER_PHASE_MERGE_INVENTORY phase. */ struct @@ -477,6 +521,7 @@ struct OrderContext { ORDER_PHASE_PARSE_REQUEST, ORDER_PHASE_PARSE_ORDER, + ORDER_PHASE_PARSE_CHOICES, ORDER_PHASE_MERGE_INVENTORY, ORDER_PHASE_ADD_PAYMENT_DETAILS, ORDER_PHASE_SET_EXCHANGES, @@ -634,6 +679,16 @@ clean_order (void *cls) json_decref (oc->merge_inventory.products); oc->merge_inventory.products = NULL; } + // TODO: Check if this is even correct + for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) + { + GNUNET_array_grow (oc->parse_choices.choices[i].inputs, + oc->parse_choices.choices[i].inputs_len, + 0); + GNUNET_array_grow (oc->parse_choices.choices[i].outputs, + oc->parse_choices.choices[i].outputs_len, + 0); + } GNUNET_array_grow (oc->parse_request.inventory_products, oc->parse_request.inventory_products_length, 0); @@ -1262,6 +1317,145 @@ get_exchange_keys (void *cls, rx); } +/** + * Fetch details about the token family with the given @a slug + * and add them to the list of token authorities. Check if the + * token family already has a valid key configured and if not, + * create a new one. + * + * @param[in,out] oc order context + * @param slug slug of the token family + * @param start_date validity start date of the token + */ +static MHD_RESULT +set_token_authority (struct OrderContext *oc, + const char *slug, + struct GNUNET_TIME_Timestamp start_date) +{ + struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details; + struct TALER_MerchantContractTokenAuthority authority; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Absolute min_start_date = GNUNET_TIME_absolute_subtract ( + start_date.abs_time, + // TODO: make this configurable. This is the granularity of token + // expiration dates. + GNUNET_TIME_UNIT_DAYS + ); + + qs = TMH_db->lookup_token_family_key (TMH_db->cls, + oc->hc->instance->settings.id, + slug, + GNUNET_TIME_absolute_to_timestamp (min_start_date), + start_date, + &key_details); + + if (qs <= 0) + { + enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + unsigned int http_status = 0; + + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + ec = TALER_EC_GENERIC_DB_FETCH_FAILED; + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + ec = TALER_EC_GENERIC_DB_SOFT_FAILURE; + break; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Token family slug unknown\n"); + http_status = MHD_HTTP_NOT_FOUND; + /* TODO: Add specific error code */ + ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* case listed to make compilers happy */ + GNUNET_assert (0); + } + GNUNET_break (0); + reply_with_error (oc, + http_status, + ec, + "authority_label"); + return MHD_NO; + } + + if (NULL == key_details.pub) + { + /* If public key is NULL, private key must also be NULL */ + GNUNET_assert (NULL == key_details.priv); + + struct GNUNET_CRYPTO_BlindSignPrivateKey *priv; + struct GNUNET_CRYPTO_BlindSignPublicKey *pub; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Timestamp valid_before = GNUNET_TIME_absolute_to_timestamp( + GNUNET_TIME_absolute_add (now, + key_details.token_family.duration)); + + // TODO: Should I add a wrapper around this, so it directly accepts structs of type + // TALER_TokenFamilyPublicKey and TALER_TokenFamilyPrivateKey? + GNUNET_CRYPTO_blind_sign_keys_create (&priv, + &pub, + // TODO: Make cipher and key length configurable + GNUNET_CRYPTO_BSA_RSA, + 4096); + + struct TALER_TokenFamilyPublicKey token_pub = { + .public_key = *pub, + }; + struct TALER_TokenFamilyPrivateKey token_priv = { + .private_key = *priv, + }; + + qs = TMH_db->insert_token_family_key (TMH_db->cls, + slug, + &token_pub, + &token_priv, + GNUNET_TIME_absolute_to_timestamp (now), + valid_before); + + authority.token_expiration = valid_before; + authority.pub = &token_pub; + // GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key); + } else { + authority.token_expiration = key_details.valid_before; + authority.pub = key_details.pub; + } + + authority.label = slug; + authority.description = key_details.token_family.description; + authority.description_i18n = key_details.token_family.description_i18n; + + GNUNET_free (key_details.token_family.slug); + GNUNET_free (key_details.token_family.name); + if (NULL != key_details.priv) { + GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key); + } + + switch (key_details.token_family.kind) { + case TALER_MERCHANTDB_TFK_Subscription: + authority.kind = TALER_MCTK_SUBSCRIPTION; + authority.details.subscription.start_date = key_details.valid_after; + authority.details.subscription.end_date = key_details.valid_before; + authority.critical = true; + // TODO: Set trusted domains + break; + case TALER_MERCHANTDB_TFK_Discount: + authority.kind = TALER_MCTK_DISCOUNT; + authority.critical = false; + // TODO: Set expected domains + break; + } + + GNUNET_array_append (oc->parse_choices.authorities, + oc->parse_choices.authorities_len, + authority); + + return MHD_YES; +} /** * Serialize order into @a oc->serialize_order.contract, @@ -1276,45 +1470,159 @@ serialize_order (struct OrderContext *oc) const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; json_t *merchant; + json_t *token_types = json_object (); + json_t *choices = json_array (); merchant = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("name", - settings->name), + settings->name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("website", - settings->website)), + settings->website)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("email", - settings->email)), + settings->email)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("logo", - settings->logo))); + settings->logo))); GNUNET_assert (NULL != merchant); { - json_t *loca = settings->address; + json_t *loca; + /* Handle merchant address */ + loca = settings->address; if (NULL != loca) { + loca = json_deep_copy (loca); + GNUNET_assert (NULL != loca); GNUNET_assert (0 == - json_object_set (merchant, - "address", - loca)); + json_object_set_new (merchant, + "address", + loca)); } } { - json_t *juri = settings->jurisdiction; + json_t *juri; /* Handle merchant jurisdiction */ + juri = settings->jurisdiction; if (NULL != juri) { + juri = json_deep_copy (juri); + GNUNET_assert (NULL != juri); GNUNET_assert (0 == - json_object_set (merchant, - "jurisdiction", - juri)); + json_object_set_new (merchant, + "jurisdiction", + juri)); } } + for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) + { + struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i]; + + // TODO: Finish spec to clearly define how token families are stored in + // ContractTerms. + // Here are some thoughts: + // - Multiple keys of the same token family can be referrenced in + // one contract. E.g. exchanging old subscription for new. + // - TokenAuthority should be renamed to TokenFamily for consistency. + // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but + // every token-based in- or ouput needs to have a valid_after date, + // so it's clear with key is referenced. + json_t *jauthority = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("description", + authority->description), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("description_i18n", + authority->description_i18n)), + GNUNET_JSON_pack_data_auto ("h_pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_data_auto ("pub", + &authority->pub->public_key.pub_key_hash), + GNUNET_JSON_pack_timestamp ("token_expiration", + authority->token_expiration) + ); + + GNUNET_assert (0 == + json_object_set_new (token_types, + authority->label, + jauthority)); + } + + for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) + { + struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i]; + + json_t *inputs = json_array (); + json_t *outputs = json_array (); + + for (unsigned int j = 0; j<choice->inputs_len; j++) + { + struct TALER_MerchantContractInput *input = &choice->inputs[j]; + + json_t *jinput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + input->type) + ); + + if (TALER_MCIT_TOKEN == input->type) + { + GNUNET_assert(0 == + json_object_set_new(jinput, + "number", + json_integer ( + input->details.token.count))); + GNUNET_assert(0 == + json_object_set_new(jinput, + "token_family_slug", + json_string ( + input->details.token.token_family_slug))); + } + + GNUNET_assert (0 == json_array_append_new (inputs, jinput)); + } + + for (unsigned int j = 0; j<choice->outputs_len; j++) + { + struct TALER_MerchantContractOutput *output = &choice->outputs[j]; + + json_t *joutput = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_int64 ("type", + output->type) + ); + + if (TALER_MCOT_TOKEN == output->type) + { + GNUNET_assert(0 == + json_object_set_new(joutput, + "number", + json_integer ( + output->details.token.count))); + + GNUNET_assert(0 == + json_object_set_new(joutput, + "token_family_slug", + json_string ( + output->details.token.token_family_slug))); + } + + GNUNET_assert (0 == json_array_append (outputs, joutput)); + } + + json_t *jchoice = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("inputs", + inputs), + GNUNET_JSON_pack_array_incref ("outputs", + outputs) + ); + + GNUNET_assert (0 == json_array_append (choices, jchoice)); + } + 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 ( @@ -1368,6 +1676,14 @@ serialize_order (struct OrderContext *oc) TALER_JSON_pack_amount ("amount", &oc->parse_order.brutto), GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("choices", + choices) + ), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("token_types", + token_types) + ), + GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("extra", (json_t *) oc->parse_order.extra)) ); @@ -1378,6 +1694,7 @@ serialize_order (struct OrderContext *oc) "refund_deadline", GNUNET_JSON_from_timestamp ( oc->parse_order.refund_deadline))); + GNUNET_log ( GNUNET_ERROR_TYPE_INFO, "Refund deadline for contact is %llu\n", @@ -1401,7 +1718,6 @@ serialize_order (struct OrderContext *oc) oc->phase++; } - /** * Set max_fee in @a oc based on STEFAN value if * not yet present. Upon success, continue @@ -1437,7 +1753,6 @@ set_max_fee (struct OrderContext *oc) oc->phase++; } - /** * Set list of acceptable exchanges in @a oc. Upon success, continue * processing with set_max_fee(). @@ -1503,8 +1818,8 @@ set_exchanges (struct OrderContext *oc) /** - * Add missing fields to the order. Upon success, continue - * processing with merge_inventory(). + * Parse the order field of the request. Upon success, continue + * processing with parse_choices(). * * @param[in,out] oc order context */ @@ -1514,12 +1829,17 @@ parse_order (struct OrderContext *oc) const struct TALER_MERCHANTDB_InstanceSettings *settings = &oc->hc->instance->settings; const char *merchant_base_url = NULL; + const char *version = NULL; const json_t *jmerchant = NULL; /* auto_refund only needs to be type-checked, * mostly because in GNUnet relative times can't * be negative. */ bool no_fee; struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("version", + &version), + NULL), TALER_JSON_spec_amount_any ("amount", &oc->parse_order.brutto), GNUNET_JSON_spec_string ("summary", @@ -1537,10 +1857,6 @@ parse_order (struct OrderContext *oc) &oc->parse_order.order_id), 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 ( GNUNET_JSON_spec_string ("fulfillment_message", &oc->parse_order.fulfillment_message), NULL), @@ -1553,6 +1869,14 @@ parse_order (struct OrderContext *oc) &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 ( + GNUNET_JSON_spec_array_const ("choices", + &oc->parse_order.choices), + NULL), + GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_web_url ("merchant_base_url", &merchant_base_url), NULL), @@ -1616,6 +1940,46 @@ parse_order (struct OrderContext *oc) ret); return; } + if (NULL == version || 0 == strcmp("0", version)) + { + oc->parse_order.version = TALER_MCV_V0; + + if (NULL != oc->parse_order.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; + } + } + else if (0 == strcmp("1", version)) + { + oc->parse_order.version = TALER_MCV_V1; + + if (! json_is_array(oc->parse_order.choices)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "order.choices is not a valid array"); + return; + } + } + else + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_VERSION_MALFORMED, + "invalid version specified in order, supported are null, '0' or '1'"); + return; + } if (! TMH_test_exchange_configured_for_currency ( oc->parse_order.brutto.currency)) { @@ -1628,9 +1992,9 @@ parse_order (struct OrderContext *oc) return; } if ( (! no_fee) && - (GNUNET_OK != - TALER_amount_cmp_currency (&oc->parse_order.brutto, - &oc->parse_order.max_fee)) ) + (GNUNET_OK != + TALER_amount_cmp_currency (&oc->parse_order.brutto, + &oc->parse_order.max_fee)) ) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); @@ -1907,6 +2271,263 @@ parse_order (struct OrderContext *oc) oc->phase++; } +/** + * Parse contract choices. Upon success, continue + * processing with merge_inventory(). + * + * @param[in,out] oc order context + */ +static void +parse_choices (struct OrderContext *oc) +{ + if (NULL == oc->parse_order.choices) + { + oc->phase++; + return; + } + + GNUNET_array_grow (oc->parse_choices.choices, + oc->parse_choices.choices_len, + json_array_size (oc->parse_order.choices)); + for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++) + { + const char *error_name; + unsigned int error_line; + const json_t *jinputs; + const json_t *joutputs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("inputs", + &jinputs), + GNUNET_JSON_spec_array_const ("outputs", + &joutputs), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue ret; + + ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i), + spec, + &error_name, + &error_line); + if (GNUNET_OK != ret) + { + 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 (! json_is_array (jinputs) || + ! json_is_array (joutputs)) + { + GNUNET_break_op (0); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "inputs or outputs"); + return; + } + + { + // TODO: Maybe move to a separate function + const json_t *jinput; + size_t idx; + json_array_foreach ((json_t *) jinputs, idx, jinput) + { + // TODO: Assuming this struct would have more fields, would i use GNUNET_new then? + // Or should i use GNUNET_grow first and then get the element using the index? + // Assuming you add a field in the future, it's easier to that way, so you don't + // free it. + struct TALER_MerchantContractInput input = {.details.token.count = 1}; + const char *kind; + const char *ierror_name; + unsigned int ierror_line; + + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("kind", + &kind), + GNUNET_JSON_spec_string ("token_family_slug", + &input.details.token.token_family_slug), + GNUNET_JSON_spec_timestamp ("valid_after", + &input.details.token.valid_after), + 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 (jinput, + ispec, + &ierror_name, + &ierror_line)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid input #%u for field %s\n", + (unsigned int) idx, + ierror_name); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + ierror_name); + return; + } + + input.type = TMH_string_to_contract_input_type (kind); + + if (TALER_MCIT_INVALID == input.type) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'kind' invalid in input #%u\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "kind"); + return; + } + + if (0 == input.details.token.count) + { + /* Ignore inputs with 'number' field set to 0 */ + continue; + } + + bool found = false; + + for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) + { + if (0 == strcmp (oc->parse_choices.authorities[i].label, + input.details.token.token_family_slug)) + { + found = true; + break; + } + } + + if (! found) + { + MHD_RESULT res; + res = set_token_authority (oc, + input.details.token.token_family_slug, + input.details.token.valid_after); + + if (MHD_NO == res) + { + return; + } + } + + GNUNET_array_append (oc->parse_choices.choices[i].inputs, + oc->parse_choices.choices[i].inputs_len, + input); + } + } + + { + const json_t *joutput; + size_t idx; + json_array_foreach ((json_t *) joutputs, idx, joutput) + { + struct TALER_MerchantContractOutput output = { .details.token.count = 1 }; + const char *kind; + const char *ierror_name; + unsigned int ierror_line; + + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("kind", + &kind), + GNUNET_JSON_spec_string ("token_family_slug", + &output.details.token.token_family_slug), + GNUNET_JSON_spec_timestamp ("valid_after", + &output.details.token.valid_after), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("count", + &output.details.token.count), + NULL), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (joutput, + ispec, + &ierror_name, + &ierror_line)) + { + GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (ispec); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid output #%u for field %s\n", + (unsigned int) idx, + ierror_name); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + ierror_name); + return; + } + + output.type = TMH_string_to_contract_output_type (kind); + + if (TALER_MCOT_INVALID == output.type) + { + GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (ispec); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Field 'kind' invalid in output #%u\n", + (unsigned int) idx); + reply_with_error (oc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "kind"); + return; + } + + if (0 == output.details.token.count) + { + /* Ignore outputs with 'number' field set to 0 */ + continue; + } + + bool found = false; + + for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++) + { + if (0 == strcmp (oc->parse_choices.authorities[i].label, + output.details.token.token_family_slug)) + { + found = true; + break; + } + } + + if (! found) + { + MHD_RESULT res; + res = set_token_authority (oc, + output.details.token.token_family_slug, + output.details.token.valid_after); + + if (MHD_NO == res) + { + return; + } + } + + GNUNET_array_append (oc->parse_choices.choices[i].outputs, + oc->parse_choices.choices[i].outputs_len, + output); + } + } + } + + oc->phase++; +} /** * Process the @a payment_target and add the details of how the @@ -2305,6 +2926,9 @@ TMH_private_post_orders ( case ORDER_PHASE_PARSE_ORDER: parse_order (oc); break; + case ORDER_PHASE_PARSE_CHOICES: + parse_choices (oc); + break; case ORDER_PHASE_MERGE_INVENTORY: merge_inventory (oc); break; |