gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant] branch master updated: -fix order creation logic for co


From: gnunet
Subject: [taler-merchant] branch master updated: -fix order creation logic for contract v1
Date: Tue, 24 Dec 2024 14:12:07 +0100

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository merchant.

The following commit(s) were added to refs/heads/master by this push:
     new 801f1890 -fix order creation logic for contract v1
801f1890 is described below

commit 801f18909fc696d37a160551233f0749db6f7d68
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Tue Dec 24 14:12:00 2024 +0100

    -fix order creation logic for contract v1
---
 src/backend/taler-merchant-httpd_contract.h        |   98 +-
 .../taler-merchant-httpd_private-post-orders.c     | 1478 +++++++++++++-------
 src/include/taler_merchant_testing_lib.h           |    4 +-
 src/testing/test_merchant_api.c                    |    8 +-
 src/testing/test_merchant_order_creation.sh        |    4 +-
 src/testing/testing_api_cmd_post_orders.c          |  167 +--
 6 files changed, 1119 insertions(+), 640 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_contract.h 
b/src/backend/taler-merchant-httpd_contract.h
index d937c119..182e7732 100644
--- a/src/backend/taler-merchant-httpd_contract.h
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -220,6 +220,9 @@ struct TALER_MerchantContractOutput
        */
       unsigned int count;
 
+      // FIXME: add support for clients picking a validity
+      // period in the future for output tokens!
+
     } token;
 
   } details;
@@ -380,10 +383,9 @@ struct TALER_MerchantContractTokenFamily
   } details;
 };
 
+
 /**
- * Struct to hold contract terms in v0 and v1 format. v0 contracts are modelled
- * as a v1 contract with a single choice and no inputs and outputs. Use the
- * version field to explicitly differentiate between v0 and v1 contracts.
+ * Struct to hold contract terms.
  */
 struct TALER_MerchantContract
 {
@@ -436,15 +438,8 @@ struct TALER_MerchantContract
      * Jurisdiction of the business
      */
     json_t *jurisdiction;
-  } merchant;
 
-  /**
-   * Price to be paid for the transaction. Could be 0. The price is in addition
-   * to other instruments, such as rations and tokens.
-   * The exchange will subtract deposit fees from that amount
-   * before transferring it to the merchant.
-   */
-  struct TALER_Amount brutto;
+  } merchant;
 
   /**
    * Summary of the contract.
@@ -531,44 +526,81 @@ struct TALER_MerchantContract
   enum TALER_MerchantContractVersion version;
 
   /**
-   * Array of possible specific contracts the wallet/customer may choose
-   * from by selecting the respective index when signing the deposit
-   * confirmation.
+   * Details depending on the @e version.
    */
-  struct TALER_MerchantContractChoice *choices;
+  union
+  {
 
-  /**
-   * Length of the @e choices array.
-   */
-  unsigned int choices_len;
+    /**
+     * Details for v0 contracts.
+     */
+    struct
+    {
 
-  /**
-   * Array of token authorities.
-   */
-  struct TALER_MerchantContractTokenFamily *token_authorities;
+      /**
+       * Price to be paid for the transaction. Could be 0. The price is in 
addition
+       * to other instruments, such as rations and tokens.
+       * The exchange will subtract deposit fees from that amount
+       * before transferring it to the merchant.
+       */
+      struct TALER_Amount brutto;
 
-  /**
-   * Length of the @e token_authorities array.
-   */
-  unsigned int token_authorities_len;
+      /**
+      * Maximum fee as given by the client request.
+      */
+      struct TALER_Amount max_fee;
 
-  /**
-    * Maximum fee as given by the client request.
-    */
-  struct TALER_Amount max_fee;
+    } v0;
+
+    /**
+     * Details for v1 contracts.
+     */
+    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 authorities.
+       */
+      struct TALER_MerchantContractTokenFamily *token_authorities;
+
+      /**
+       * Length of the @e token_authorities array.
+       */
+      unsigned int token_authorities_len;
+
+    } v1;
+
+  } details;
+
+  // FIXME: Add exchanges array?
 
-  // TODO: Add exchanges array
 };
 
+
 enum TALER_MerchantContractInputType
 TMH_contract_input_type_from_string (const char *str);
 
+
 enum TALER_MerchantContractOutputType
 TMH_contract_output_type_from_string (const char *str);
 
+
 const char *
 TMH_string_from_contract_input_type (enum TALER_MerchantContractInputType t);
 
+
 const char *
 TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t);
 
@@ -590,12 +622,14 @@ TMH_serialize_contract (const struct 
TALER_MerchantContract *contract,
                         json_t *exchanges,
                         json_t **out);
 
+
 enum GNUNET_GenericReturnValue
 TMH_serialize_contract_v0 (const struct TALER_MerchantContract *contract,
                            const struct TMH_MerchantInstance *instance,
                            json_t *exchanges,
                            json_t **out);
 
+
 enum GNUNET_GenericReturnValue
 TMH_serialize_contract_v1 (const struct TALER_MerchantContract *contract,
                            const struct TMH_MerchantInstance *instance,
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c 
b/src/backend/taler-merchant-httpd_private-post-orders.c
index 836efb27..1cb11a15 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -247,10 +247,6 @@ struct OrderContext
    */
   struct
   {
-    /**
-     * Version of the contract terms.
-     */
-    enum TALER_MerchantContractVersion version;
 
     /**
      * Our order ID.
@@ -294,11 +290,6 @@ struct OrderContext
      */
     const char *public_reorder_url;
 
-    /**
-    * Array of contract choices. Is null for v0 contracts.
-    */
-    const json_t *choices;
-
     /**
      * Merchant base URL.
      */
@@ -334,17 +325,6 @@ struct OrderContext
      */
     const json_t *delivery_location;
 
-    /**
-    * Gross amount value of the contract. Used to
-    * compute @e max_stefan_fee.
-    */
-    struct TALER_Amount brutto;
-
-    /**
-     * Maximum fee as given by the client request.
-     */
-    struct TALER_Amount max_fee;
-
     /**
      * Specifies for how long the wallet should try to get an
      * automatic refund for the purchase.
@@ -367,6 +347,45 @@ struct OrderContext
      */
     uint32_t minimum_age;
 
+    /**
+     * Version of the contract terms.
+     */
+    enum TALER_MerchantContractVersion version;
+
+    /**
+     * Details present depending on @e version.
+     */
+    union
+    {
+      /**
+       * Details only present for v0.
+       */
+      struct
+      {
+        /**
+         * Gross amount value of the contract. Used to
+         * compute @e max_stefan_fee.
+         */
+        struct TALER_Amount brutto;
+
+        /**
+         * Maximum fee as given by the client request.
+         */
+        struct TALER_Amount max_fee;
+      } v0;
+
+      /**
+       * Details only present for v1.
+       */
+      struct
+      {
+        /**
+         * Array of contract choices. Is null for v0 contracts.
+         */
+        const json_t *choices;
+      } v1;
+    } details;
+
   } parse_order;
 
   /**
@@ -445,18 +464,16 @@ struct OrderContext
     bool exchange_good;
 
     /**
-     * Maximum fee for @e order based on STEFAN curves.
-     * Used to set @e max_fee if not provided as part of
-     * @e order.
+     * Array of maximum amounts that could be paid over all
+     * available exchanges. Used to determine if this
+     * order creation requests exceeds legal limits.
      */
-    struct TALER_Amount max_stefan_fee;
+    struct TALER_Amount *total_exchange_limits;
 
     /**
-     * Maximum amount that could be paid over all
-     * available exchanges. Used to determine if this
-     * order creation requests exceeds legal limits.
+     * Length of the @e total_exchange_limits array.
      */
-    struct TALER_Amount total_exchange_limit;
+    unsigned int num_total_exchange_limits;
 
     /**
      * How long do we wait at most until giving up on getting keys?
@@ -468,6 +485,43 @@ struct OrderContext
      */
     struct GNUNET_SCHEDULER_Task *wakeup_task;
 
+    /**
+     * Details depending on the contract version.
+     */
+    union
+    {
+
+      /**
+       * Details for contract v0.
+       */
+      struct
+      {
+        /**
+         * Maximum fee for @e order based on STEFAN curves.
+         * Used to set @e max_fee if not provided as part of
+         * @e order.
+         */
+        struct TALER_Amount max_stefan_fee;
+
+      } v0;
+
+      /**
+       * Details for contract v1.
+       */
+      struct
+      {
+        /**
+         * Maximum fee for @e order based on STEFAN curves by
+         * contract choice.
+         * Used to set @e max_fee if not provided as part of
+         * @e order.
+         */
+        struct TALER_Amount *max_stefan_fees;
+
+      } v1;
+
+    } details;
+
   } set_exchanges;
 
   /**
@@ -475,10 +529,37 @@ struct OrderContext
    */
   struct
   {
+
     /**
-     * Maximum fee
+     * Details depending on the contract version.
      */
-    struct TALER_Amount max_fee;
+    union
+    {
+
+      /**
+       * Details for contract v0.
+       */
+      struct
+      {
+        /**
+         * Maximum fee
+         */
+        struct TALER_Amount max_fee;
+      } v0;
+
+      /**
+       * Details for contract v1.
+       */
+      struct
+      {
+        /**
+         * Maximum fees by contract choice.
+         */
+        struct TALER_Amount *max_fees;
+
+      } v1;
+
+    } details;
   } set_max_fee;
 
   /**
@@ -693,6 +774,16 @@ clean_order (void *cls)
     json_decref (oc->set_exchanges.exchanges);
     oc->set_exchanges.exchanges = NULL;
   }
+  GNUNET_free (oc->set_exchanges.total_exchange_limits);
+  switch (oc->parse_order.version)
+  {
+  case TALER_MCV_V0:
+    break;
+  case TALER_MCV_V1:
+    GNUNET_free (oc->set_max_fee.details.v1.max_fees);
+    GNUNET_free (oc->set_exchanges.details.v1.max_stefan_fees);
+    break;
+  }
   if (NULL != oc->merge_inventory.products)
   {
     json_decref (oc->merge_inventory.products);
@@ -1790,86 +1881,38 @@ add_output_token_family (struct OrderContext *oc,
 
 
 /**
- * Serialize order into @a oc->serialize_order.contract,
- * ready to be stored in the database. Upon success, continue
- * processing with check_contract().
+ * Build JSON array that represents all of the token families
+ * in the contract.
  *
- * @param[in,out] oc order context
+ * @param[in] v1-style order
+ * @return JSON array with token families for the contract
  */
-static void
-serialize_order (struct OrderContext *oc)
+static json_t *
+output_token_families (struct OrderContext *oc)
 {
-  const struct TALER_MERCHANTDB_InstanceSettings *settings =
-    &oc->hc->instance->settings;
-  json_t *merchant;
   json_t *token_families = json_object ();
-  json_t *choices = json_array ();
-
-  merchant = GNUNET_JSON_PACK (
-    GNUNET_JSON_pack_string ("name",
-                             settings->name),
-    GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_string ("website",
-                               settings->website)),
-    GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_string ("email",
-                               settings->email)),
-    GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_string ("logo",
-                               settings->logo)));
-  GNUNET_assert (NULL != merchant);
-  {
-    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_new (merchant,
-                                          "address",
-                                          loca));
-    }
-  }
-  {
-    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_new (merchant,
-                                          "jurisdiction",
-                                          juri));
-    }
-  }
 
+  GNUNET_assert (NULL != token_families);
   for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
   {
-    json_t *keys = json_array ();
-    struct TALER_MerchantContractTokenFamily *family
+    const struct TALER_MerchantContractTokenFamily *family
       = &oc->parse_choices.token_families[i];
+    json_t *keys;
     json_t *jfamily;
 
+    keys = json_array ();
+    GNUNET_assert (NULL != keys);
     for (unsigned int j = 0; j<family->keys_len; j++)
     {
-      struct TALER_MerchantContractTokenFamilyKey key = family->keys[j];
-
+      const struct TALER_MerchantContractTokenFamilyKey *key
+        = &family->keys[j];
       json_t *jkey = GNUNET_JSON_PACK (
-        /* TODO: Remove h_pub. */
-        GNUNET_JSON_pack_data_auto ("h_pub",
-                                    &key.pub.public_key->pub_key_hash),
         TALER_JSON_pack_token_pub ("public_key",
-                                   &key.pub),
+                                   &key->pub),
         GNUNET_JSON_pack_timestamp ("valid_after",
-                                    key.valid_after),
+                                    key->valid_after),
         GNUNET_JSON_pack_timestamp ("valid_before",
-                                    key.valid_before)
+                                    key->valid_before)
         );
 
       GNUNET_assert (0 ==
@@ -1877,7 +1920,7 @@ serialize_order (struct OrderContext *oc)
                                             jkey));
     }
 
-    /* TODO: Add 'details' field. */
+    /* FIXME: Add 'details' field. */
     jfamily = GNUNET_JSON_PACK (
       GNUNET_JSON_pack_string ("name",
                                family->name),
@@ -1892,11 +1935,28 @@ serialize_order (struct OrderContext *oc)
                              family->critical)
       );
 
-    GNUNET_assert (0 == json_object_set_new (token_families,
-                                             family->slug,
-                                             jfamily));
+    GNUNET_assert (0 ==
+                   json_object_set_new (token_families,
+                                        family->slug,
+                                        jfamily));
   }
+  return token_families;
+}
+
+
+/**
+ * Build JSON array that represents all of the contract choices
+ * in the contract.
+ *
+ * @param[in] v1-style order
+ * @return JSON array with token families for the contract
+ */
+static json_t *
+output_contract_choices (struct OrderContext *oc)
+{
+  json_t *choices = json_array ();
 
+  GNUNET_assert (NULL != choices);
   for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
   {
     const struct TALER_MerchantContractChoice *choice
@@ -1912,15 +1972,15 @@ serialize_order (struct OrderContext *oc)
         = &choice->inputs[j];
       json_t *jinput;
 
-      /* For now, only tokens are supported */
+      /* For now, only tokens are supported for inputs */
       GNUNET_assert (TALER_MCIT_TOKEN == input->type);
       jinput = GNUNET_JSON_PACK (
         GNUNET_JSON_pack_string ("kind",
-                                 TMH_string_from_contract_input_type (input->
-                                                                      type)),
+                                 TMH_string_from_contract_input_type (
+                                   input->type)),
         GNUNET_JSON_pack_string ("token_family_slug",
                                  input->details.token.token_family_slug),
-        GNUNET_JSON_pack_int64 ("number",
+        GNUNET_JSON_pack_int64 ("count",
                                 input->details.token.count)
         );
 
@@ -1935,19 +1995,47 @@ serialize_order (struct OrderContext *oc)
       json_t *joutput;
 
       /* For now, only tokens are supported */
-      GNUNET_assert (TALER_MCOT_TOKEN == output->type);
-
-      joutput = GNUNET_JSON_PACK (
-        GNUNET_JSON_pack_string ("kind",
-                                 TMH_string_from_contract_output_type (output->
-                                                                       type)),
-        GNUNET_JSON_pack_string ("token_family_slug",
-                                 output->details.token.token_family_slug),
-        GNUNET_JSON_pack_int64 ("number",
-                                output->details.token.count),
-        GNUNET_JSON_pack_int64 ("key_index",
-                                output->details.token.key_index)
-        );
+      switch (output->type)
+      {
+      case TALER_MCOT_INVALID:
+        /* How did we get here? */
+        GNUNET_assert (0);
+        /* mostly to make compiler happy... */
+        finalize_order (oc,
+                        MHD_NO);
+        json_decref (choices);
+        return NULL;
+      case TALER_MCOT_TOKEN:
+        joutput = GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_string ("kind",
+                                   TMH_string_from_contract_output_type (
+                                     output->type)),
+          GNUNET_JSON_pack_string ("token_family_slug",
+                                   output->details.token.token_family_slug),
+          GNUNET_JSON_pack_int64 ("count",
+                                  output->details.token.count),
+          GNUNET_JSON_pack_int64 ("key_index",
+                                  output->details.token.key_index)
+          );
+        break;
+      case TALER_MCOT_COIN:
+        /* Not implemented, how did we get here? */
+        GNUNET_break (0);
+        reply_with_error (oc,
+                          MHD_HTTP_NOT_IMPLEMENTED,
+                          TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                          "currency conversion not supported");
+        json_decref (choices);
+        return NULL;
+      case TALER_MCOT_TAX_RECEIPT:
+        // FIXME: generate JSON for DONAU here instead of killing
+        // the connection!
+        GNUNET_break (0);
+        finalize_order (oc,
+                        MHD_NO);
+        json_decref (choices);
+        return NULL;
+      }
 
       GNUNET_assert (0 ==
                      json_array_append_new (outputs,
@@ -1957,6 +2045,10 @@ serialize_order (struct OrderContext *oc)
     {
       json_t *jchoice
         = GNUNET_JSON_PACK (
+            TALER_JSON_pack_amount ("amount",
+                                    &choice->amount),
+            TALER_JSON_pack_amount ("max_fee",
+                                    &oc->set_max_fee.details.v1.max_fees[i]),
             GNUNET_JSON_pack_array_incref ("inputs",
                                            inputs),
             GNUNET_JSON_pack_array_incref ("outputs",
@@ -1967,6 +2059,67 @@ serialize_order (struct OrderContext *oc)
                      json_array_append_new (choices,
                                             jchoice));
     }
+  } /* for all choices */
+  return choices;
+}
+
+
+/**
+ * Serialize order into @a oc->serialize_order.contract,
+ * ready to be stored in the database. Upon success, continue
+ * processing with check_contract().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+serialize_order (struct OrderContext *oc)
+{
+  const struct TALER_MERCHANTDB_InstanceSettings *settings =
+    &oc->hc->instance->settings;
+  json_t *merchant;
+
+  merchant = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("name",
+                             settings->name),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("website",
+                               settings->website)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("email",
+                               settings->email)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("logo",
+                               settings->logo)));
+  GNUNET_assert (NULL != merchant);
+  {
+    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_new (merchant,
+                                          "address",
+                                          loca));
+    }
+  }
+  {
+    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_new (merchant,
+                                          "jurisdiction",
+                                          juri));
+    }
   }
 
   oc->serialize_order.contract = GNUNET_JSON_PACK (
@@ -1975,8 +2128,9 @@ serialize_order (struct OrderContext *oc)
     GNUNET_JSON_pack_string ("summary",
                              oc->parse_order.summary),
     GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_object_incref ("summary_i18n",
-                                      (json_t *) 
oc->parse_order.summary_i18n)),
+      GNUNET_JSON_pack_object_incref (
+        "summary_i18n",
+        (json_t *) oc->parse_order.summary_i18n)),
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_string ("public_reorder_url",
                                oc->parse_order.public_reorder_url)),
@@ -1984,10 +2138,9 @@ serialize_order (struct OrderContext *oc)
       GNUNET_JSON_pack_string ("fulfillment_message",
                                oc->parse_order.fulfillment_message)),
     GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n",
-                                      (json_t *) oc->parse_order.
-                                      fulfillment_message_i18n))
-    ,
+      GNUNET_JSON_pack_object_incref (
+        "fulfillment_message_i18n",
+        (json_t *) oc->parse_order.fulfillment_message_i18n)),
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_string ("fulfillment_url",
                                oc->parse_order.fulfillment_url)),
@@ -2012,9 +2165,9 @@ serialize_order (struct OrderContext *oc)
       GNUNET_JSON_pack_timestamp ("delivery_date",
                                   oc->parse_order.delivery_date)),
     GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_object_incref ("delivery_location",
-                                      (json_t *) oc->parse_order.
-                                      delivery_location)),
+      GNUNET_JSON_pack_object_incref (
+        "delivery_location",
+        (json_t *) oc->parse_order.delivery_location)),
     GNUNET_JSON_pack_string ("merchant_base_url",
                              oc->parse_order.merchant_base_url),
     GNUNET_JSON_pack_object_steal ("merchant",
@@ -2023,43 +2176,61 @@ serialize_order (struct OrderContext *oc)
                                 &oc->hc->instance->merchant_pub),
     GNUNET_JSON_pack_array_incref ("exchanges",
                                    oc->set_exchanges.exchanges),
-    TALER_JSON_pack_amount ("max_fee",
-                            &oc->set_max_fee.max_fee),
-    TALER_JSON_pack_amount ("amount",
-                            &oc->parse_order.brutto),
-    GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_array_steal ("choices",
-                                    choices)
-      ),
-    GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_object_steal ("token_families",
-                                     token_families)
-      ),
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_object_incref ("extra",
                                       (json_t *) oc->parse_order.extra))
     );
 
+  {
+    json_t *xtra;
+
+    switch (oc->parse_order.version)
+    {
+    case TALER_MCV_V0:
+      xtra = GNUNET_JSON_PACK (
+        TALER_JSON_pack_amount ("max_fee",
+                                &oc->set_max_fee.details.v0.max_fee),
+        TALER_JSON_pack_amount ("amount",
+                                &oc->parse_order.details.v0.brutto));
+      break;
+    case TALER_MCV_V1:
+      {
+        json_t *token_families = output_token_families (oc);
+        json_t *choices = output_contract_choices (oc);
+
+        if ( (NULL == token_families) ||
+             (NULL == choices) )
+        {
+          GNUNET_break (0);
+          return;
+        }
+        xtra = GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_array_steal ("choices",
+                                        choices),
+          GNUNET_JSON_pack_object_steal ("token_families",
+                                         token_families));
+        break;
+      }
+    default:
+      GNUNET_assert (0);
+    }
+    GNUNET_assert (0 ==
+                   json_object_update (oc->serialize_order.contract,
+                                       xtra));
+    json_decref (xtra);
+  }
+
+
   /* Pack does not work here, because it doesn't set zero-values for 
timestamps */
   GNUNET_assert (0 ==
                  json_object_set_new (oc->serialize_order.contract,
                                       "refund_deadline",
                                       GNUNET_JSON_from_timestamp (
                                         oc->parse_order.refund_deadline)));
-
-  GNUNET_log (
-    GNUNET_ERROR_TYPE_INFO,
-    "Refund deadline for contact is %llu\n",
-    (unsigned long long) 
oc->parse_order.refund_deadline.abs_time.abs_value_us);
-  GNUNET_log (
-    GNUNET_ERROR_TYPE_INFO,
-    "Wallet timestamp for contact is %llu\n",
-    (unsigned long long) oc->parse_order.timestamp.abs_time.abs_value_us);
-
-  /* Pack does not work here, because it sets zero-values for relative times */
   /* auto_refund should only be set if it is not 0 */
   if (! GNUNET_TIME_relative_is_zero (oc->parse_order.auto_refund))
   {
+    /* Pack does not work here, because it sets zero-values for relative times 
*/
     GNUNET_assert (0 ==
                    json_object_set_new (oc->serialize_order.contract,
                                         "auto_refund",
@@ -2072,36 +2243,78 @@ serialize_order (struct OrderContext *oc)
 
 
 /**
- * Set max_fee in @a oc based on STEFAN value if
- * not yet present. Upon success, continue
- * processing with serialize_order().
+ * Set @a max_fee in @a oc based on @a max_stefan_fee value if not overridden
+ * by @a client_fee.  If neither is set, set the fee to zero using currency
+ * from @a brutto.
  *
  * @param[in,out] oc order context
+ * @param brutto brutto amount to compute fee for
+ * @param client_fee client-given fee override (or invalid)
+ * @param max_stefan_fee maximum STEFAN fee of any exchange
+ * @param max_fee set to the maximum stefan fee
  */
 static void
-set_max_fee (struct OrderContext *oc)
+compute_fee (struct OrderContext *oc,
+             const struct TALER_Amount *brutto,
+             const struct TALER_Amount *client_fee,
+             const struct TALER_Amount *max_stefan_fee,
+             struct TALER_Amount *max_fee)
 {
-  const struct TALER_MERCHANTDB_InstanceSettings *settings =
-    &oc->hc->instance->settings;
+  const struct TALER_MERCHANTDB_InstanceSettings *settings
+    = &oc->hc->instance->settings;
 
-  if (GNUNET_OK !=
-      TALER_amount_is_valid (&oc->parse_order.max_fee))
+  if (GNUNET_OK ==
+      TALER_amount_is_valid (client_fee))
   {
-    struct TALER_Amount stefan;
-
-    if ( (settings->use_stefan) &&
-         (GNUNET_OK ==
-          TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) )
-      stefan = oc->set_exchanges.max_stefan_fee;
-    else
-      GNUNET_assert (GNUNET_OK ==
-                     TALER_amount_set_zero (oc->parse_order.brutto.currency,
-                                            &stefan));
-    oc->set_max_fee.max_fee = stefan;
+    *max_fee = *client_fee;
+    return;
   }
-  else
+  if ( (settings->use_stefan) &&
+       (GNUNET_OK ==
+        TALER_amount_is_valid (max_stefan_fee)) )
+  {
+    *max_fee = *max_stefan_fee;
+    return;
+  }
+  GNUNET_assert (
+    GNUNET_OK ==
+    TALER_amount_set_zero (brutto->currency,
+                           max_fee));
+}
+
+
+/**
+ * Initialize "set_max_fee" in @a oc based on STEFAN value or client
+ * preference. Upon success, continue processing in next phase.
+ *
+ * @param[in,out] oc order context
+ */
+static void
+set_max_fee (struct OrderContext *oc)
+{
+  switch (oc->parse_order.version)
   {
-    oc->set_max_fee.max_fee = oc->parse_order.max_fee;
+  case TALER_MCV_V0:
+    compute_fee (oc,
+                 &oc->parse_order.details.v0.brutto,
+                 &oc->parse_order.details.v0.max_fee,
+                 &oc->set_exchanges.details.v0.max_stefan_fee,
+                 &oc->set_max_fee.details.v0.max_fee);
+    break;
+  case TALER_MCV_V1:
+    oc->set_max_fee.details.v1.max_fees
+      = GNUNET_new_array (oc->parse_choices.choices_len,
+                          struct TALER_Amount);
+    for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+      compute_fee (oc,
+                   &oc->parse_choices.choices[i].amount,
+                   &oc->parse_choices.choices[i].max_fee,
+                   &oc->set_exchanges.details.v1.max_stefan_fees[i],
+                   &oc->set_max_fee.details.v1.max_fees[i]);
+    break;
+  default:
+    GNUNET_break (0);
+    break;
   }
   oc->phase++;
 }
@@ -2130,51 +2343,89 @@ resume_with_keys (struct OrderContext *oc)
 
 
 /**
- * Update MAX STEFAN fees based on @a keys.
+ * Given a @a brutto amount for exchange with @a keys, set the
+ * @a stefan_fee. Note that @a stefan_fee is updated to the maximum
+ * of the input and the computed fee.
  *
- * @param[in,out] oc order context to update
- * @param keys keys to derive STEFAN fees from
+ * @param[in,out] oc order context
+ * @param brutto some brutto amount the client is to pay
+ * @param[in,out] stefan_fee set to STEFAN fee to be paid by the merchant
  */
 static void
-update_stefan (struct OrderContext *oc,
-               const struct TALER_EXCHANGE_Keys *keys)
+compute_stefan_fee (const struct TALER_EXCHANGE_Keys *keys,
+                    const struct TALER_Amount *brutto,
+                    struct TALER_Amount *stefan_fee)
 {
   struct TALER_Amount net;
 
   if (GNUNET_SYSERR !=
       TALER_EXCHANGE_keys_stefan_b2n (keys,
-                                      &oc->parse_order.brutto,
+                                      brutto,
                                       &net))
   {
     struct TALER_Amount fee;
 
     TALER_EXCHANGE_keys_stefan_round (keys,
                                       &net);
-    if (-1 == TALER_amount_cmp (&oc->parse_order.brutto,
+    if (-1 == TALER_amount_cmp (brutto,
                                 &net))
     {
       /* brutto < netto! */
       /* => after rounding, there is no real difference */
-      net = oc->parse_order.brutto;
+      net = *brutto;
     }
     GNUNET_assert (0 <=
                    TALER_amount_subtract (&fee,
-                                          &oc->parse_order.brutto,
+                                          brutto,
                                           &net));
     if ( (GNUNET_OK !=
-          TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) ||
-         (-1 == TALER_amount_cmp (&oc->set_exchanges.max_stefan_fee,
+          TALER_amount_is_valid (stefan_fee)) ||
+         (-1 == TALER_amount_cmp (stefan_fee,
                                   &fee)) )
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   "Updated STEFAN-based fee to %s\n",
                   TALER_amount2s (&fee));
-      oc->set_exchanges.max_stefan_fee = fee;
+      *stefan_fee = fee;
     }
   }
 }
 
 
+/**
+ * Update MAX STEFAN fees based on @a keys.
+ *
+ * @param[in,out] oc order context to update
+ * @param keys keys to derive STEFAN fees from
+ */
+static void
+update_stefan (struct OrderContext *oc,
+               const struct TALER_EXCHANGE_Keys *keys)
+{
+  switch (oc->parse_order.version)
+  {
+  case TALER_MCV_V0:
+    compute_stefan_fee (keys,
+                        &oc->parse_order.details.v0.brutto,
+                        &oc->set_exchanges.details.v0.max_stefan_fee);
+    break;
+  case TALER_MCV_V1:
+    oc->set_exchanges.details.v1.max_stefan_fees
+      = GNUNET_new_array (oc->parse_choices.choices_len,
+                          struct TALER_Amount);
+    for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+      if (0 == strcasecmp (keys->currency,
+                           oc->parse_choices.choices[i].amount.currency))
+        compute_stefan_fee (keys,
+                            &oc->parse_choices.choices[i].amount,
+                            &oc->set_exchanges.details.v1.max_stefan_fees[i]);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+}
+
+
 /**
  * Compute the set of exchanges that would be acceptable
  * for this order.
@@ -2182,11 +2433,13 @@ update_stefan (struct OrderContext *oc,
  * @param cls our `struct OrderContext`
  * @param url base URL of an exchange (not used)
  * @param exchange internal handle for the exchange
+ * @param max_needed maximum amount needed in this currency
  */
 static void
 get_acceptable (void *cls,
                 const char *url,
-                const struct TMH_Exchange *exchange)
+                const struct TMH_Exchange *exchange,
+                const struct TALER_Amount *max_needed)
 {
   struct OrderContext *oc = cls;
   unsigned int priority = 42; /* make compiler happy */
@@ -2194,7 +2447,7 @@ get_acceptable (void *cls,
   enum GNUNET_GenericReturnValue res;
   struct TALER_Amount max_amount;
 
-  max_amount = oc->parse_order.brutto;
+  max_amount = *max_needed;
   res = TMH_exchange_check_debit (
     oc->hc->instance->settings.id,
     exchange,
@@ -2205,8 +2458,7 @@ get_acceptable (void *cls,
               url,
               res,
               TALER_amount2s (&max_amount));
-  if ( (! TALER_amount_is_zero (&max_amount)) &&
-       (TALER_amount_is_zero (&max_amount)) )
+  if (TALER_amount_is_zero (&max_amount))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Exchange %s deposit limit is zero, skipping it\n",
@@ -2236,20 +2488,47 @@ get_acceptable (void *cls,
               "Exchange %s deposit limit is %s, adding it!\n",
               url,
               TALER_amount2s (&max_amount));
-  GNUNET_assert (0 <=
-                 TALER_amount_add (
-                   &oc->set_exchanges.total_exchange_limit,
-                   &oc->set_exchanges.total_exchange_limit,
-                   &max_amount));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_min (&oc->set_exchanges.total_exchange_limit,
-                                   &oc->set_exchanges.total_exchange_limit,
-                                   &oc->parse_order.brutto));
-  j_exchange = GNUNET_JSON_PACK (
-    GNUNET_JSON_pack_string ("url",
-                             url),
-    GNUNET_JSON_pack_uint64 ("priority",
-                             priority),
+  {
+    bool found = false;
+
+    for (unsigned int i = 0; i<oc->set_exchanges.num_total_exchange_limits; 
i++)
+    {
+      struct TALER_Amount *limit
+        = &oc->set_exchanges.total_exchange_limits[i];
+
+      if (GNUNET_OK ==
+          TALER_amount_cmp_currency (limit,
+                                     &max_amount))
+      {
+        GNUNET_assert (0 <=
+                       TALER_amount_add (limit,
+                                         limit,
+                                         &max_amount));
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_amount_min (limit,
+                                         limit,
+                                         max_needed));
+        found = true;
+      }
+    }
+    if (! found)
+    {
+      struct TALER_Amount limit;
+
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_amount_min (&limit,
+                                       &max_amount,
+                                       max_needed));
+      GNUNET_array_append (oc->set_exchanges.total_exchange_limits,
+                           oc->set_exchanges.num_total_exchange_limits,
+                           limit);
+    }
+  }
+  j_exchange = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("url",
+                             url),
+    GNUNET_JSON_pack_uint64 ("priority",
+                             priority),
     TALER_JSON_pack_amount ("max_contribution",
                             &max_amount),
     GNUNET_JSON_pack_data_auto ("master_pub",
@@ -2292,17 +2571,59 @@ keys_cb (
   }
   else
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Got response for %skeys\n",
-                rx->url);
-    if ( (settings->use_stefan) &&
-         (GNUNET_OK !=
-          TALER_amount_is_valid (&oc->parse_order.max_fee)) )
-      update_stefan (oc,
-                     keys);
-    get_acceptable (oc,
-                    rx->url,
-                    exchange);
+    bool currency_ok = false;
+    struct TALER_Amount max_needed;
+
+    switch (oc->parse_order.version)
+    {
+    case TALER_MCV_V0:
+      if (0 == strcasecmp (keys->currency,
+                           oc->parse_order.details.v0.brutto.currency))
+      {
+        max_needed = oc->parse_order.details.v0.brutto;
+        currency_ok = true;
+      }
+      break;
+    case TALER_MCV_V1:
+      for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+      {
+        const struct TALER_Amount *amount
+          = &oc->parse_choices.choices[i].amount;
+
+        if (0 == strcasecmp (keys->currency,
+                             amount->currency))
+        {
+          if (currency_ok)
+          {
+            TALER_amount_max (&max_needed,
+                              &max_needed,
+                              amount);
+          }
+          else
+          {
+            max_needed = *amount;
+            currency_ok = true;
+          }
+        }
+      }
+      break;
+    default:
+      GNUNET_assert (0);
+    }
+    if ( (currency_ok) &&
+         (! TALER_amount_is_zero (&max_needed)) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Got response for %skeys\n",
+                  rx->url);
+      if (settings->use_stefan)
+        update_stefan (oc,
+                       keys);
+      get_acceptable (oc,
+                      rx->url,
+                      exchange,
+                      &max_needed);
+    }
   }
   GNUNET_free (rx->url);
   GNUNET_free (rx);
@@ -2409,6 +2730,40 @@ wakeup_timeout (void *cls)
 }
 
 
+/**
+ * Check that the @a brutto amount is at or below the exchange
+ * limits we have for the respective currency.
+ *
+ * @param oc order context to check
+ * @param brutto amount to check
+ * @param true if the amount is OK, false if it is too high
+ */
+static bool
+check_exchange_limits (const struct OrderContext *oc,
+                       struct TALER_Amount *brutto)
+{
+  for (unsigned int i = 0; i<oc->set_exchanges.num_total_exchange_limits; i++)
+  {
+    const struct TALER_Amount *total_exchange_limit
+      = &oc->set_exchanges.total_exchange_limits[i];
+
+    if (GNUNET_OK !=
+        TALER_amount_cmp_currency (brutto,
+                                   total_exchange_limit))
+      continue;
+    if (1 !=
+        TALER_amount_cmp (brutto,
+                          total_exchange_limit))
+      return true;
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "Cannot create order: %s is above the sum of hard limits from 
supported exchanges\n",
+              TALER_amount2s (brutto));
+  return false;
+}
+
+
 /**
  * Set list of acceptable exchanges in @a oc. Upon success, continue
  * processing with set_max_fee().
@@ -2419,12 +2774,32 @@ wakeup_timeout (void *cls)
 static bool
 set_exchanges (struct OrderContext *oc)
 {
+  bool need_exchange;
+
   if (NULL != oc->set_exchanges.wakeup_task)
   {
     GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
     oc->set_exchanges.wakeup_task = NULL;
   }
-  if (TALER_amount_is_zero (&oc->parse_order.brutto))
+  switch (oc->parse_order.version)
+  {
+  case TALER_MCV_V0:
+    need_exchange = ! TALER_amount_is_zero (
+      &oc->parse_order.details.v0.brutto);
+    break;
+  case TALER_MCV_V1:
+    need_exchange = false;
+    for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+      if (! TALER_amount_is_zero (&oc->parse_choices.choices[i].amount))
+      {
+        need_exchange = true;
+        break;
+      }
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  if (! need_exchange)
   {
     /* Total amount is zero, so we don't actually need exchanges! */
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -2435,21 +2810,13 @@ set_exchanges (struct OrderContext *oc)
     return false;
   }
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Order total is %s, trying to find exchanges\n",
-              TALER_amount2s (&oc->parse_order.brutto));
-  /* Note: re-building 'oc->set_exchanges.exchanges' every time here might be a
-     tad expensive; could likely consider caching the result if it starts to
-     matter. */
+              "Trying to find exchanges\n");
   if (NULL == oc->set_exchanges.exchanges)
   {
     oc->set_exchanges.keys_timeout
       = GNUNET_TIME_relative_to_absolute (MAX_KEYS_WAIT);
     oc->set_exchanges.exchanges = json_array ();
     GNUNET_assert (NULL != oc->set_exchanges.exchanges);
-    GNUNET_assert (
-      GNUNET_OK ==
-      TALER_amount_set_zero (oc->parse_order.brutto.currency,
-                             &oc->set_exchanges.total_exchange_limit));
     TMH_exchange_get_trusted (&get_exchange_keys,
                               oc);
   }
@@ -2505,32 +2872,52 @@ set_exchanges (struct OrderContext *oc)
       oc->add_payment_details.wm->wire_method);
     return false;
   }
-  if (1 ==
-      TALER_amount_cmp (&oc->parse_order.brutto,
-                        &oc->set_exchanges.total_exchange_limit))
+
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Cannot create order: %s is the sum of hard limits from 
supported exchanges\n",
-                TALER_amount2s (&oc->set_exchanges.total_exchange_limit));
-    notify_kyc_required (oc);
-    reply_with_error (
-      oc,
-      MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
-      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS,
-      TALER_amount2s (&oc->set_exchanges.total_exchange_limit));
-    return false;
+    bool ok;
+    struct TALER_Amount ea;
+
+    switch (oc->parse_order.version)
+    {
+    case TALER_MCV_V0:
+      ea = oc->parse_order.details.v0.brutto;
+      ok = check_exchange_limits (oc,
+                                  &ea);
+      break;
+    case TALER_MCV_V1:
+      ok = true;
+      for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+      {
+        ea = oc->parse_choices.choices[i].amount;
+        if (! check_exchange_limits (oc,
+                                     &ea))
+        {
+          ok = false;
+          break;
+        }
+      }
+      break;
+    default:
+      GNUNET_assert (0);
+    }
+
+    if (! ok)
+    {
+      notify_kyc_required (oc);
+      reply_with_error (
+        oc,
+        MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS,
+        TALER_amount2s (&ea));
+      return false;
+    }
   }
+
   if (! oc->set_exchanges.exchange_good)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Creating order, but possibly without usable trusted 
exchanges\n");
   }
-  else
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Can create order: %s is the sum of hard limits from supported 
exchanges\n",
-                TALER_amount2s (&oc->set_exchanges.total_exchange_limit));
-  }
   oc->phase++;
   return false;
 }
@@ -2550,18 +2937,12 @@ parse_order (struct OrderContext *oc)
   const char *merchant_base_url = NULL;
   uint64_t version = 0;
   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;
-  const char *oid;
+  const char *order_id;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_uint64 ("version",
                                &version),
       NULL),
-    TALER_JSON_spec_amount_any ("amount",
-                                &oc->parse_order.brutto),
     GNUNET_JSON_spec_string ("summary",
                              &oc->parse_order.summary),
     GNUNET_JSON_spec_mark_optional (
@@ -2574,7 +2955,7 @@ parse_order (struct OrderContext *oc)
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("order_id",
-                               &oid),
+                               &order_id),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("fulfillment_message",
@@ -2592,14 +2973,11 @@ parse_order (struct OrderContext *oc)
       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),
+    /* For sanity check, this field must NOT be present */
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_object_const ("merchant",
                                      &jmerchant),
@@ -2620,10 +2998,6 @@ parse_order (struct OrderContext *oc)
       GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
                                   &oc->parse_order.wire_deadline),
       NULL),
-    GNUNET_JSON_spec_mark_optional (
-      TALER_JSON_spec_amount_any ("max_fee",
-                                  &oc->parse_order.max_fee),
-      &no_fee),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_object_const ("delivery_location",
                                      &oc->parse_order.delivery_location),
@@ -2660,36 +3034,100 @@ parse_order (struct OrderContext *oc)
                      ret);
     return;
   }
-  if (0 == version)
+  switch (version)
   {
-    oc->parse_order.version = TALER_MCV_V0;
-    if (NULL != oc->parse_order.choices)
+  case 0:
     {
-      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;
+      bool no_fee;
+      const json_t *choices = NULL;
+      struct GNUNET_JSON_Specification specv0[] = {
+        TALER_JSON_spec_amount_any (
+          "amount",
+          &oc->parse_order.details.v0.brutto),
+        GNUNET_JSON_spec_mark_optional (
+          TALER_JSON_spec_amount_any (
+            "max_fee",
+            &oc->parse_order.details.v0.max_fee),
+          &no_fee),
+        /* for sanity check, must be *absent*! */
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_array_const ("choices",
+                                        &choices),
+          NULL),
+        GNUNET_JSON_spec_end ()
+      };
+
+      ret = TALER_MHD_parse_json_data (oc->connection,
+                                       oc->parse_request.order,
+                                       specv0);
+      if (GNUNET_OK != ret)
+      {
+        GNUNET_break_op (0);
+        finalize_order2 (oc,
+                         ret);
+        return;
+      }
+      if ( (! no_fee) &&
+           (GNUNET_OK !=
+            TALER_amount_cmp_currency (&oc->parse_order.details.v0.brutto,
+                                       &oc->parse_order.details.v0.max_fee)) )
+      {
+        GNUNET_break_op (0);
+        GNUNET_JSON_parse_free (spec);
+        reply_with_error (oc,
+                          MHD_HTTP_BAD_REQUEST,
+                          TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                          "different currencies used for 'max_fee' and 
'amount' currency");
+        return;
+      }
+      if (! TMH_test_exchange_configured_for_currency (
+            oc->parse_order.details.v0.brutto.currency))
+      {
+        GNUNET_break_op (0);
+        GNUNET_JSON_parse_free (spec);
+        // FIXME: use CONFLICT and a different EC!
+        reply_with_error (oc,
+                          MHD_HTTP_BAD_REQUEST,
+                          TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                          "no trusted exchange for this currency");
+        return;
+      }
+      if (NULL != choices)
+      {
+        GNUNET_break_op (0);
+        GNUNET_JSON_parse_free (spec);
+        reply_with_error (oc,
+                          MHD_HTTP_BAD_REQUEST,
+                          TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
+                          "choices array must be null for v0 contracts");
+        return;
+      }
+      oc->parse_order.version = TALER_MCV_V0;
+      break;
     }
-  }
-  else if (1 == version)
-  {
-    oc->parse_order.version = TALER_MCV_V1;
-    if (NULL == oc->parse_order.choices)
+  case 1:
     {
-      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 required in v1 contracts");
-      return;
+      struct GNUNET_JSON_Specification specv1[] = {
+        GNUNET_JSON_spec_array_const (
+          "choices",
+          &oc->parse_order.details.v1.choices),
+        GNUNET_JSON_spec_end ()
+      };
+
+      ret = TALER_MHD_parse_json_data (oc->connection,
+                                       oc->parse_request.order,
+                                       specv1);
+      if (GNUNET_OK != ret)
+      {
+        GNUNET_break_op (0);
+        finalize_order2 (oc,
+                         ret);
+        return;
+      }
+      oc->parse_order.version = TALER_MCV_V1;
+      break;
     }
-  }
-  else
-  {
+  default:
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
     reply_with_error (oc,
@@ -2698,35 +3136,11 @@ parse_order (struct OrderContext *oc)
                       "invalid version specified in order, supported are null, 
'0' or '1'");
     return;
   }
-  if (! TMH_test_exchange_configured_for_currency (
-        oc->parse_order.brutto.currency))
-  {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
-    reply_with_error (oc,
-                      MHD_HTTP_BAD_REQUEST,
-                      TALER_EC_GENERIC_CURRENCY_MISMATCH,
-                      "no trusted exchange for this currency");
-    return;
-  }
-  if ( (! no_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);
-    reply_with_error (oc,
-                      MHD_HTTP_BAD_REQUEST,
-                      TALER_EC_GENERIC_CURRENCY_MISMATCH,
-                      "different currencies used for 'max_fee' and 'amount' 
currency");
-    return;
-  }
 
   /* Add order_id if it doesn't exist. */
-  if (NULL != oid)
+  if (NULL != order_id)
   {
-    oc->parse_order.order_id = GNUNET_strdup (oid);
+    oc->parse_order.order_id = GNUNET_strdup (order_id);
   }
   else
   {
@@ -3000,6 +3414,217 @@ parse_order (struct OrderContext *oc)
 }
 
 
+/**
+ * Parse the inputs for a particular choice.
+ *
+ * @param[in,out] oc order context
+ * @param[out] choice to parse inputs for
+ * @param jinputs array of inputs to parse
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR
+ *   if an error was encountered (and already handled)
+ */
+static enum GNUNET_GenericReturnValue
+parse_order_inputs (struct OrderContext *oc,
+                    struct TALER_MerchantContractChoice *choice,
+                    const json_t *jinputs)
+{
+  const json_t *jinput;
+  size_t idx;
+
+  json_array_foreach ((json_t *) jinputs, idx, jinput)
+  {
+    struct TALER_MerchantContractInput input = {
+      .details.token.count = 1
+    };
+    const char *kind;
+    const char *ierror_name;
+    unsigned int ierror_line;
+    struct GNUNET_JSON_Specification ispec[] = {
+      // FIXME: define spec parser for 'kind'...
+      GNUNET_JSON_spec_string ("kind",
+                               &kind),
+      GNUNET_JSON_spec_string ("token_family_slug",
+                               &input.details.token.token_family_slug),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_uint32 ("count",
+                                 &input.details.token.count),
+        NULL),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (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 GNUNET_SYSERR;
+    }
+
+    input.type = TMH_contract_input_type_from_string (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 GNUNET_SYSERR;
+    }
+
+    if (0 == input.details.token.count)
+    {
+      /* Ignore inputs with 'number' field set to 0 */
+      continue;
+    }
+
+    if (GNUNET_OK !=
+        add_input_token_family (oc,
+                                input.details.token.token_family_slug))
+    {
+      /* error is already scheduled, return. */
+      return GNUNET_SYSERR;
+    }
+
+    GNUNET_array_append (choice->inputs,
+                         choice->inputs_len,
+                         input);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse the outputs for a particular choice.
+ *
+ * @param[in,out] oc order context
+ * @param[out] choice to parse inputs for
+ * @param joutputs array of outputs to parse
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR
+ *   if an error was encountered (and already handled)
+ */
+static enum GNUNET_GenericReturnValue
+parse_order_outputs (struct OrderContext *oc,
+                     struct TALER_MerchantContractChoice *choice,
+                     const json_t *joutputs)
+{
+  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;
+    bool nots;
+    struct GNUNET_TIME_Timestamp valid_at;
+    struct GNUNET_JSON_Specification ispec[] = {
+      // FIXME: define spec parser for 'kind'...
+      GNUNET_JSON_spec_string ("kind",
+                               &kind),
+      GNUNET_JSON_spec_string ("token_family_slug",
+                               &output.details.token.token_family_slug),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_uint32 ("count",
+                                 &output.details.token.count),
+        NULL),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_timestamp ("valid_at",
+                                    &valid_at),
+        &nots),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (joutput,
+                           ispec,
+                           &ierror_name,
+                           &ierror_line))
+    {
+      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 GNUNET_SYSERR;
+    }
+    if (nots)
+    {
+      valid_at = oc->parse_order.pay_deadline;
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Looking for output token valid at pay deadline %s\n",
+                  GNUNET_TIME_timestamp2s (valid_at));
+    }
+    else
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Looking for output token valid at %s\n",
+                  GNUNET_TIME_timestamp2s (valid_at));
+    }
+    if (GNUNET_TIME_timestamp_cmp (valid_at,
+                                   <,
+                                   oc->parse_order.pay_deadline))
+    {
+      reply_with_error (oc,
+                        MHD_HTTP_BAD_REQUEST,
+                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                        "valid_at before pay_deadline");
+      return GNUNET_SYSERR;
+    }
+
+    output.type = TMH_contract_output_type_from_string (kind);
+    if (TALER_MCOT_INVALID == output.type)
+    {
+      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 GNUNET_SYSERR;
+    }
+
+    if (0 == output.details.token.count)
+    {
+      /* Ignore outputs with 'number' field set to 0. */
+      continue;
+    }
+
+    if (GNUNET_OK !=
+        add_output_token_family (oc,
+                                 output.details.token.token_family_slug,
+                                 valid_at,
+                                 &output.details.token.key_index))
+    {
+      /* Error is already scheduled, return. */
+      return GNUNET_SYSERR;
+    }
+
+    GNUNET_array_append (choice->outputs,
+                         choice->outputs_len,
+                         output);
+  }
+  return GNUNET_OK;
+}
+
+
 /**
  * Parse contract choices. Upon success, continue
  * processing with merge_inventory().
@@ -3009,15 +3634,24 @@ parse_order (struct OrderContext *oc)
 static void
 parse_choices (struct OrderContext *oc)
 {
-  if (NULL == oc->parse_order.choices)
+  const json_t *choices;
+
+  switch (oc->parse_order.version)
   {
+  case TALER_MCV_V0:
     oc->phase++;
     return;
+  case TALER_MCV_V1:
+    /* handle below */
+    break;
+  default:
+    GNUNET_assert (0);
   }
 
+  choices = oc->parse_order.details.v1.choices;
   GNUNET_array_grow (oc->parse_choices.choices,
                      oc->parse_choices.choices_len,
-                     json_array_size (oc->parse_order.choices));
+                     json_array_size (choices));
   for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
   {
     struct TALER_MerchantContractChoice *choice
@@ -3026,13 +3660,14 @@ parse_choices (struct OrderContext *oc)
     unsigned int error_line;
     const json_t *jinputs;
     const json_t *joutputs;
+    bool no_fee;
     struct GNUNET_JSON_Specification spec[] = {
       TALER_JSON_spec_amount_any ("amount",
                                   &choice->amount),
       GNUNET_JSON_spec_mark_optional (
         TALER_JSON_spec_amount_any ("max_fee",
                                     &choice->max_fee),
-        NULL),
+        &no_fee),
       GNUNET_JSON_spec_mark_optional (
         GNUNET_JSON_spec_array_const ("inputs",
                                       &jinputs),
@@ -3045,7 +3680,7 @@ parse_choices (struct OrderContext *oc)
     };
     enum GNUNET_GenericReturnValue ret;
 
-    ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices,
+    ret = GNUNET_JSON_parse (json_array_get (choices,
                                              i),
                              spec,
                              &error_name,
@@ -3062,6 +3697,33 @@ parse_choices (struct OrderContext *oc)
                         "choice");
       return;
     }
+    if ( (! no_fee) &&
+         (GNUNET_OK !=
+          TALER_amount_cmp_currency (&choice->amount,
+                                     &choice->max_fee)) )
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      reply_with_error (oc,
+                        MHD_HTTP_BAD_REQUEST,
+                        TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                        "different currencies used for 'max_fee' and 'amount' 
currency");
+      return;
+    }
+
+    if (! TMH_test_exchange_configured_for_currency (
+          choice->amount.currency))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      // FIXME: use CONFLICT and a different EC!
+      reply_with_error (oc,
+                        MHD_HTTP_BAD_REQUEST,
+                        TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                        "no trusted exchange for this currency");
+      return;
+    }
+
 
     if ( (0 == json_array_size (jinputs)) &&
          (0 == json_array_size (joutputs)) )
@@ -3075,195 +3737,17 @@ parse_choices (struct OrderContext *oc)
                         "choice");
       return;
     }
-
-    {
-      // TODO: Maybe move to a separate function
-      const json_t *jinput;
-      size_t idx;
-      json_array_foreach ((json_t *) jinputs, idx, jinput)
-      {
-        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_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_contract_input_type_from_string (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;
-        }
-
-        if (GNUNET_OK !=
-            add_input_token_family (oc,
-                                    input.details.token.token_family_slug))
-        {
-          /* error is already scheduled, return. */
-          return;
-        }
-
-        GNUNET_array_append (choice->inputs,
-                             choice->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;
-        bool nots;
-        struct GNUNET_TIME_Timestamp valid_at;
-        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_mark_optional (
-            GNUNET_JSON_spec_uint32 ("count",
-                                     &output.details.token.count),
-            NULL),
-          GNUNET_JSON_spec_mark_optional (
-            GNUNET_JSON_spec_timestamp ("valid_at",
-                                        &valid_at),
-            &nots),
-          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;
-        }
-        if (nots)
-        {
-          valid_at = oc->parse_order.pay_deadline;
-          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                      "Looking for output token valid at pay deadline %s\n",
-                      GNUNET_TIME_timestamp2s (valid_at));
-        }
-        else
-        {
-          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                      "Looking for output token valid at %s\n",
-                      GNUNET_TIME_timestamp2s (valid_at));
-        }
-
-
-        if (GNUNET_TIME_timestamp_cmp (valid_at,
-                                       <,
-                                       oc->parse_order.pay_deadline))
-        {
-          GNUNET_JSON_parse_free (spec);
-          GNUNET_JSON_parse_free (ispec);
-          reply_with_error (oc,
-                            MHD_HTTP_BAD_REQUEST,
-                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                            "valid_at before pay_deadline");
-          return;
-        }
-
-        output.type = TMH_contract_output_type_from_string (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;
-        }
-
-        if (GNUNET_OK !=
-            add_output_token_family (oc,
-                                     output.details.token.token_family_slug,
-                                     valid_at,
-                                     &output.details.token.key_index))
-        {
-          /* Error is already scheduled, return. */
-          return;
-        }
-
-        GNUNET_array_append (choice->outputs,
-                             choice->outputs_len,
-                             output);
-      }
-    }
+    if (GNUNET_OK !=
+        parse_order_inputs (oc,
+                            choice,
+                            jinputs))
+      return;
+    if (GNUNET_OK !=
+        parse_order_outputs (oc,
+                             choice,
+                             joutputs))
+      return;
   }
-
   oc->phase++;
 }
 
diff --git a/src/include/taler_merchant_testing_lib.h 
b/src/include/taler_merchant_testing_lib.h
index d3bf0d1c..e405d49a 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -613,7 +613,7 @@ TALER_TESTING_cmd_merchant_post_orders3 (
  * @param merchant_url base URL of the merchant serving
  *        the proposal request.
  * @param http_status expected HTTP status.
- * @param token_family_reference label of the POST /tokenfamilies cmd.
+ * @param token_family_slug slug of the token family to use
  * @param num_inputs number of input tokens.
  * @param num_outputs number of output tokens.
  * @param order_id the name of the order to add.
@@ -629,7 +629,7 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
   const struct GNUNET_CONFIGURATION_Handle *cfg,
   const char *merchant_url,
   unsigned int http_status,
-  const char *token_family_reference,
+  const char *token_family_slug,
   unsigned int num_inputs,
   unsigned int num_outputs,
   const char *order_id,
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index 24118031..99a54281 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -1714,7 +1714,7 @@ run (void *cls,
       cred.cfg,
       merchant_url,
       MHD_HTTP_OK,
-      "create-upcoming-tokenfamily",
+      "subscription-upcoming",
       0,
       1,
       "5-upcoming-output",
@@ -1740,7 +1740,7 @@ run (void *cls,
       cred.cfg,
       merchant_url,
       MHD_HTTP_OK,
-      "create-tokenfamily",
+      "subscription-1",
       0,
       1,
       "5-output",
@@ -1763,7 +1763,7 @@ run (void *cls,
       cred.cfg,
       merchant_url,
       MHD_HTTP_OK,
-      "create-tokenfamily",
+      "subscription-1",
       1,
       1,
       "5-input-output",
@@ -1796,7 +1796,7 @@ run (void *cls,
       cred.cfg,
       merchant_url,
       MHD_HTTP_OK,
-      "create-tokenfamily",
+      "subscription-1",
       1,
       1,
       "5-input-output-2",
diff --git a/src/testing/test_merchant_order_creation.sh 
b/src/testing/test_merchant_order_creation.sh
index d55f7ce1..cb70c22e 100755
--- a/src/testing/test_merchant_order_creation.sh
+++ b/src/testing/test_merchant_order_creation.sh
@@ -266,11 +266,11 @@ fi
 echo " OK"
 
 echo "curl 'http://localhost:9966/private/orders' \
-      -d 
'{\"order\":{\"version\":1,\"amount\":\"TESTKUDOS:7\",\"summary\":\"with_subscription\",\"fulfillment_message\":\"Paid
 
successfully\",\"choices\":[{\"inputs\":[{\"kind\":\"token\",\"count\":1,\"token_family_slug\":\"test-sub\",\"valid_after\":{\"t_s\":'$NOW'}}],\"outputs\":[{\"kind\":\"token\",\"count\":1,\"token_family_slug\":\"test-sub\",\"valid_after\":{\"t_s\":'$NOW'}}]}]}}'"
+      -d 
'{\"order\":{\"version\":1,\"summary\":\"with_subscription\",\"fulfillment_message\":\"Paid
 
successfully\",\"choices\":[\{\"amount\":\"TESTKUDOS:7","inputs\":[{\"kind\":\"token\",\"count\":1,\"token_family_slug\":\"test-sub\",\"valid_after\":{\"t_s\":'$NOW'}}],\"outputs\":[{\"kind\":\"token\",\"count\":1,\"token_family_slug\":\"test-sub\",\"valid_after\":{\"t_s\":'$NOW'}}]}]}}'"
 
 echo -n "Creating v1 order with token family ..."
 STATUS=$(curl 'http://localhost:9966/private/orders' \
-    -d 
'{"order":{"version":1,"amount":"TESTKUDOS:7","summary":"with_subscription","fulfillment_message":"Paid
 
successfully","choices":[{"inputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}],"outputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}]}]}}'
 \
+    -d 
'{"order":{"version":1,"summary":"with_subscription","fulfillment_message":"Paid
 
successfully","choices":[{"amount":"TESTKUDOS:7","inputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}],"outputs":[{"kind":"token","count":1,"token_family_slug":"test-sub","valid_after":{"t_s":'$NOW'}}]}]}}'
 \
     -w "%{http_code}" -s -o "$LAST_RESPONSE")
 
 if [ "$STATUS" != "200" ]
diff --git a/src/testing/testing_api_cmd_post_orders.c 
b/src/testing/testing_api_cmd_post_orders.c
index 04b258eb..bef760bf 100644
--- a/src/testing/testing_api_cmd_post_orders.c
+++ b/src/testing/testing_api_cmd_post_orders.c
@@ -59,23 +59,6 @@ struct OrdersState
    */
   const char *expected_order_id;
 
-  /**
-   * Reference to a POST /tokenfamilies command. Can be NULL.
-   */
-  const char *token_family_reference;
-
-  /**
-   * How many tokens of the token family created in
-   * @a token_family_reference are required as inputs.
-   */
-  unsigned int num_inputs;
-
-  /**
-   * How many tokens of the token family created in
-   * @a token_family_reference should be issued as outputs.
-   */
-  unsigned int num_outputs;
-
   /**
    * Contract terms obtained from the backend.
    */
@@ -86,11 +69,6 @@ struct OrdersState
    */
   json_t *order_terms;
 
-  /**
-   * Choices array with inputs and outputs for v1 order.
-   */
-  json_t *choices;
-
   /**
    * Contract terms hash code.
    */
@@ -588,48 +566,6 @@ orders_run2 (void *cls,
 }
 
 
-/**
- * Constructs the json for a the choices of an order request.
- *
- * @param input_slug the name of the token family to use for input, can be NULL
- * @param output_slug the name of the token family to use for the output, can 
be NULL.
- * @param input_count number of token inputs to require
- * @param output_count number of tokens to output
- * @param input_valid_after validity date for the input token.
- * @param output_valid_after validity date for the output token.
- * @param[out] choices where to write the json string.
- */
-static void
-make_choices_json (
-  const char *input_slug,
-  const char *output_slug,
-  uint16_t input_count,
-  uint16_t output_count,
-  struct GNUNET_TIME_Timestamp input_valid_after,
-  struct GNUNET_TIME_Timestamp output_valid_after,
-  json_t **choices)
-{
-  /* FIXME: ugly code should return c, use GNUNET_JSON_PACK() for more 
type-safety */
-  json_t *c;
-
-  c = json_pack ("[{s:o, s:o}]",
-                 "inputs", json_pack ("[{s:s, s:i, s:s, s:o}]",
-                                      "kind", "token",
-                                      "count", input_count,
-                                      "token_family_slug", input_slug,
-                                      "valid_after", GNUNET_JSON_from_timestamp
-                                        (input_valid_after)),
-                 "outputs", json_pack ("[{s:s, s:i, s:s, s:o}]",
-                                       "kind", "token",
-                                       "count", output_count,
-                                       "token_family_slug", output_slug,
-                                       "valid_after", 
GNUNET_JSON_from_timestamp
-                                         (output_valid_after)));
-
-  *choices = c;
-}
-
-
 /**
  * Run a "orders" CMD.
  *
@@ -644,7 +580,6 @@ orders_run3 (void *cls,
 {
   struct OrdersState *ps = cls;
   struct GNUNET_TIME_Absolute now;
-  const char *slug;
 
   ps->is = is;
   now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
@@ -663,37 +598,6 @@ orders_run3 (void *cls,
     GNUNET_free (order_id);
   }
 
-  {
-    const struct TALER_TESTING_Command *token_family_cmd;
-    token_family_cmd =
-      TALER_TESTING_interpreter_lookup_command (is,
-                                                ps->token_family_reference);
-    if (NULL == token_family_cmd)
-      TALER_TESTING_FAIL (is);
-    if (GNUNET_OK !=
-        TALER_TESTING_get_trait_token_family_slug (token_family_cmd,
-                                                   &slug))
-      TALER_TESTING_FAIL (is);
-  }
-  make_choices_json (slug, slug,
-                     ps->num_inputs,
-                     ps->num_outputs,
-                     GNUNET_TIME_absolute_to_timestamp (now),
-                     GNUNET_TIME_absolute_to_timestamp (now),
-                     &ps->choices);
-
-  GNUNET_assert (0 ==
-                 json_object_set_new (ps->order_terms,
-                                      "choices",
-                                      ps->choices)
-                 );
-  GNUNET_assert (0 ==
-                 json_object_set_new (ps->order_terms,
-                                      "version",
-                                      json_integer (1))
-                 );
-
-
   GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                               &ps->nonce,
                               sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
@@ -770,7 +674,7 @@ mark_forgettable (void *cls,
  * @param order_id the name of the order to add, can be NULL.
  * @param refund_deadline the deadline for refunds on this order.
  * @param pay_deadline the deadline for payment on this order.
- * @param amount the amount this order is for.
+ * @param amount the amount this order is for, NULL for v1 orders
  * @param[out] order where to write the json string.
  */
 static void
@@ -786,7 +690,7 @@ make_order_json (const char *order_id,
 
   /* Include required fields and some dummy objects to test forgetting. */
   contract_terms = json_pack (
-    "{s:s, s:s?, s:s, s:s, s:o, s:o, s:s, s:[{s:s}, {s:s}, {s:s}]}",
+    "{s:s, s:s?, s:s?, s:s, s:o, s:o, s:s, s:[{s:s}, {s:s}, {s:s}]}",
     "summary", "merchant-lib testcase",
     "order_id", order_id,
     "amount", amount,
@@ -981,7 +885,7 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
   const struct GNUNET_CONFIGURATION_Handle *cfg,
   const char *merchant_url,
   unsigned int http_status,
-  const char *token_family_reference,
+  const char *token_family_slug,
   unsigned int num_inputs,
   unsigned int num_outputs,
   const char *order_id,
@@ -990,18 +894,75 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
   const char *amount)
 {
   struct OrdersState *ps;
+  struct TALER_Amount brutto;
+  json_t *choice;
+  json_t *choices;
+  json_t *inputs;
+  json_t *outputs;
 
   ps = GNUNET_new (struct OrdersState);
   ps->cfg = cfg;
   make_order_json (order_id,
                    refund_deadline,
                    pay_deadline,
-                   amount,
+                   NULL,
                    &ps->order_terms);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (amount,
+                                         &brutto));
+  inputs = json_array ();
+  GNUNET_assert (NULL != inputs);
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   inputs,
+                   GNUNET_JSON_PACK (
+                     GNUNET_JSON_pack_string ("kind",
+                                              "token"),
+                     GNUNET_JSON_pack_uint64 ("count",
+                                              num_inputs),
+                     GNUNET_JSON_pack_string ("token_family_slug",
+                                              token_family_slug)
+                     )));
+  outputs = json_array ();
+  GNUNET_assert (NULL != outputs);
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   outputs,
+                   GNUNET_JSON_PACK (
+                     GNUNET_JSON_pack_string ("kind",
+                                              "token"),
+                     GNUNET_JSON_pack_uint64 ("count",
+                                              num_outputs),
+                     GNUNET_JSON_pack_string ("token_family_slug",
+                                              token_family_slug)
+                     )));
+  choice
+    = GNUNET_JSON_PACK (
+        TALER_JSON_pack_amount ("amount",
+                                &brutto),
+        GNUNET_JSON_pack_array_steal ("inputs",
+                                      inputs),
+        GNUNET_JSON_pack_array_steal ("outputs",
+                                      outputs));
+  choices = json_array ();
+  GNUNET_assert (NULL != choices);
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   choices,
+                   choice));
+  GNUNET_assert (0 ==
+                 json_object_set_new (ps->order_terms,
+                                      "choices",
+                                      choices)
+                 );
+  GNUNET_assert (0 ==
+                 json_object_set_new (ps->order_terms,
+                                      "version",
+                                      json_integer (1))
+                 );
+
+
   ps->http_status = http_status;
-  ps->token_family_reference = token_family_reference;
-  ps->num_inputs = num_inputs;
-  ps->num_outputs = num_outputs;
   ps->expected_order_id = order_id;
   ps->merchant_url = merchant_url;
   ps->with_claim = true;

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]