summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-post-orders.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c720
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;