gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated: fixing various design issues in


From: gnunet
Subject: [taler-merchant] branch master updated: fixing various design issues in token logic
Date: Tue, 10 Dec 2024 22:33:20 +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 56fd5686 fixing various design issues in token logic
56fd5686 is described below

commit 56fd568628dae06191ccb2133a2ff6b8c092be41
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Tue Dec 10 22:33:12 2024 +0100

    fixing various design issues in token logic
---
 src/backend/taler-merchant-httpd_contract.c        | 109 +--
 src/backend/taler-merchant-httpd_contract.h        | 118 +--
 .../taler-merchant-httpd_post-orders-ID-pay.c      | 252 ++++--
 ...erchant-httpd_private-get-token-families-SLUG.c |  43 +-
 ...chant-httpd_private-patch-token-families-SLUG.c |  32 +-
 .../taler-merchant-httpd_private-post-instances.c  |   5 +
 .../taler-merchant-httpd_private-post-orders.c     | 990 +++++++++++++--------
 ...er-merchant-httpd_private-post-token-families.c | 198 +++--
 src/backenddb/Makefile.am                          |   1 +
 src/backenddb/merchant-0008.sql                    |   8 +-
 src/backenddb/merchant-0013.sql                    |  40 +-
 src/backenddb/pg_insert_token_family.c             |  29 +-
 src/backenddb/pg_insert_token_family_key.c         |  46 +-
 src/backenddb/pg_insert_token_family_key.h         |  11 +-
 src/backenddb/pg_lookup_token_family.c             |  26 +-
 src/backenddb/pg_lookup_token_family_key.c         |  58 +-
 src/backenddb/pg_lookup_token_family_key.h         |  17 +-
 src/backenddb/pg_lookup_token_family_keys.c        | 225 +++++
 ..._family_key.h => pg_lookup_token_family_keys.h} |  37 +-
 src/backenddb/pg_update_token_family.c             |  20 +-
 src/backenddb/plugin_merchantdb_postgres.c         |   3 +
 src/include/taler_merchant_service.h               |  30 +-
 src/include/taler_merchantdb_plugin.h              |  89 +-
 src/lib/merchant_api_get_tokenfamily.c             |  12 +-
 src/lib/merchant_api_post_order_pay.c              |   2 +
 src/lib/merchant_api_post_tokenfamilies.c          |  20 +-
 src/testing/Makefile.am                            |   5 +-
 src/testing/test_merchant_api.c                    |  87 +-
 src/testing/test_merchant_order_creation.sh        |  14 +-
 src/testing/testing_api_cmd_pay_order.c            | 170 ++--
 src/testing/testing_api_cmd_post_tokenfamilies.c   |   8 +-
 31 files changed, 1740 insertions(+), 965 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_contract.c 
b/src/backend/taler-merchant-httpd_contract.c
index 07db6a9d..d311990a 100644
--- a/src/backend/taler-merchant-httpd_contract.c
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -22,6 +22,7 @@
 #include <gnunet/gnunet_common.h>
 #include <jansson.h>
 #include <stdint.h>
+#include <taler/taler_json_lib.h>
 #include "taler-merchant-httpd_contract.h"
 
 
@@ -143,15 +144,15 @@ parse_choices (void *cls,
       size_t idx;
       json_array_foreach ((json_t *) jinputs, idx, jinput)
       {
-        struct TALER_MerchantContractInput input = {.details.token.count = 1};
+        struct TALER_MerchantContractInput input = {
+          .details.token.count = 1
+        };
         const char *kind;
         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),
@@ -208,13 +209,14 @@ parse_choices (void *cls,
           .details.token.count = 1
         };
         const char *kind;
+        uint32_t ki;
         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_uint32 ("key_index",
+                                   &ki),
           GNUNET_JSON_spec_mark_optional (
             GNUNET_JSON_spec_uint32 ("count",
                                      &output.details.token.count),
@@ -238,7 +240,7 @@ parse_choices (void *cls,
           GNUNET_break_op (0);
           return GNUNET_SYSERR;
         }
-
+        output.details.token.key_index = ki;
         output.type = TMH_contract_output_type_from_string (kind);
 
         if (TALER_MCOT_INVALID == output.type)
@@ -310,20 +312,14 @@ parse_token_families (void *cls,
   {
     const json_t *keys;
     struct TALER_MerchantContractTokenFamily family = {
-      .slug = slug
+      .slug = GNUNET_strdup (slug)
     };
-
     struct GNUNET_JSON_Specification spec[] = {
       GNUNET_JSON_spec_array_const ("keys",
                                     &keys),
       GNUNET_JSON_spec_bool ("critical",
                              &family.critical),
       GNUNET_JSON_spec_end ()
-      /* TODO: Figure out if these fields should be 'const' */
-      // GNUNET_JSON_spec_string ("description",
-      //                          &family.description),
-      // GNUNET_JSON_spec_object_const ("description_i18n",
-      //                                &family.description_i18n),
     };
     const char *error_name;
     unsigned int error_line;
@@ -342,74 +338,40 @@ parse_token_families (void *cls,
       GNUNET_break_op (0);
       return GNUNET_SYSERR;
     }
-
     GNUNET_array_grow (family.keys,
                        family.keys_len,
                        json_array_size (keys));
 
     for (unsigned int i = 0; i<family.keys_len; i++)
     {
-      /* TODO: Move this to TALER_JSON_spec_token_issue_key */
-      int64_t cipher;
       struct TALER_MerchantContractTokenFamilyKey *key = &family.keys[i];
-      /* TODO: Free when not used anymore */
-
-      key->pub.public_key
-        = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
-      {
-        struct GNUNET_JSON_Specification key_spec[] = {
-          GNUNET_JSON_spec_fixed_auto (
-            "h_pub",
-            &key->pub.public_key->pub_key_hash),
-          GNUNET_JSON_spec_rsa_public_key (
-            "rsa_pub",
-            &key->pub.public_key->details.rsa_public_key),
-          // FIXME: sort out CS here!
-          //   GNUNET_JSON_spec_fixed_auto ("cs_pub",
-          //                               
&key.pub.public_key->details.cs_public_key)),
-          GNUNET_JSON_spec_int64 (
-            "cipher",
-            &cipher),
-          GNUNET_JSON_spec_timestamp (
-            "valid_after",
-            &key->valid_after),
-          GNUNET_JSON_spec_timestamp (
-            "valid_before",
-            &key->valid_before),
-          GNUNET_JSON_spec_end ()
-        };
-        const char *ierror_name;
-        unsigned int ierror_line;
-
-        if (GNUNET_OK !=
-            GNUNET_JSON_parse (json_array_get (keys,
-                                               i),
-                               key_spec,
-                               &ierror_name,
-                               &ierror_line))
-        {
-          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                      "Failed to parse %s at %u: %s\n",
-                      key_spec[ierror_line].field,
-                      ierror_line,
-                      ierror_name);
-          GNUNET_break_op (0);
-          return GNUNET_SYSERR;
-        }
-      }
-
-      switch (cipher)
+      struct GNUNET_JSON_Specification key_spec[] = {
+        TALER_JSON_spec_token_pub (
+          "public_key",
+          &key->pub),
+        GNUNET_JSON_spec_timestamp (
+          "valid_after",
+          &key->valid_after),
+        GNUNET_JSON_spec_timestamp (
+          "valid_before",
+          &key->valid_before),
+        GNUNET_JSON_spec_end ()
+      };
+      const char *ierror_name;
+      unsigned int ierror_line;
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json_array_get (keys,
+                                             i),
+                             key_spec,
+                             &ierror_name,
+                             &ierror_line))
       {
-      case GNUNET_CRYPTO_BSA_RSA:
-        key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_RSA;
-        break;
-      case GNUNET_CRYPTO_BSA_CS:
-        key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_CS;
-        break;
-      case GNUNET_CRYPTO_BSA_INVALID:
         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                    "Field 'cipher' invalid in key #%u\n",
-                    i);
+                    "Failed to parse %s at %u: %s\n",
+                    key_spec[ierror_line].field,
+                    ierror_line,
+                    ierror_name);
         GNUNET_break_op (0);
         return GNUNET_SYSERR;
       }
@@ -418,7 +380,6 @@ parse_token_families (void *cls,
                          *families_len,
                          family);
   }
-
   return GNUNET_OK;
 }
 
@@ -477,5 +438,5 @@ TMH_find_token_family_key (
   }
 
   /* no matching family found */
-  return GNUNET_NO;
+  return GNUNET_SYSERR;
 }
diff --git a/src/backend/taler-merchant-httpd_contract.h 
b/src/backend/taler-merchant-httpd_contract.h
index 3036dc35..36f3e9d7 100644
--- a/src/backend/taler-merchant-httpd_contract.h
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -18,6 +18,9 @@
  * @brief shared logic for contract terms handling
  * @author Christian Blättler
  */
+#ifndef TALER_MERCHANT_HTTPD_CONTRACT_H
+#define TALER_MERCHANT_HTTPD_CONTRACT_H
+
 #include "taler-merchant-httpd.h"
 #include <gnunet/gnunet_common.h>
 #include <gnunet/gnunet_time_lib.h>
@@ -93,8 +96,8 @@ struct TALER_MerchantContractInput
   union
   {
     /**
-    * Coin-based input (ration). (Future work, only here for reference)
-    */
+     * Coin-based input (ration). (Future work, only here for reference)
+     */
     // struct
     // {
     //   /**
@@ -109,25 +112,19 @@ struct TALER_MerchantContractInput
     // } coin;
 
     /**
-    * Token-based input.
-    */
+     * Token-based input.
+     */
     struct
     {
       /**
-      * Slug of the token family to be used.
-      */
+       * Slug of the token family to be used.
+       */
       const char *token_family_slug;
 
       /**
-      * Start time of the validity period of the token. Base on this timestamp
-      * the wallet can find the correct key for this token in 
token_authorities.
-      */
-      struct GNUNET_TIME_Timestamp valid_after;
-
-      /**
-      * Number of tokens of this type required. Defaults to one if the
-      * field is not provided.
-      */
+       * Number of tokens of this type required. Defaults to one if the
+       * field is not provided.
+       */
       unsigned int count;
     } token;
   } details;
@@ -167,62 +164,66 @@ enum TALER_MerchantContractOutputType
 struct TALER_MerchantContractOutput
 {
   /**
-  * Type of the output.
-  */
+   * Type of the output.
+   */
   enum TALER_MerchantContractOutputType type;
 
   union
   {
     /**
-    * Coin-based output.
-    */
+     * Coin-based output.
+     */
     struct
     {
       /**
-      * Coins that will be yielded. This excludes any applicable withdraw fees.
-      */
+       * Coins that will be yielded. This excludes any applicable withdraw 
fees.
+       */
       struct TALER_Amount brutto_yield;
 
       /**
-      * Base URL of the exchange that will issue the coins.
-      */
+       * Base URL of the exchange that will issue the coins.
+       */
       const char *exchange_url;
+
     } coin;
 
     /**
-    * Tax-receipt output.
-    */
+     * Tax-receipt output.
+     */
     struct
     {
       /**
-      * Base URL of the donation authority that will issue the tax receipt.
-      */
+       * Base URL of the donation authority that will issue the tax receipt.
+       */
       const char *donau_url;
     } tax_receipt;
 
     /**
-    * Token-based output.
-    */
+     * Token-based output.
+     */
     struct
     {
       /**
-      * Slug of the token family to be issued.
-      */
+       * Slug of the token family to be issued.
+       */
       const char *token_family_slug;
 
       /**
-      * Start time of the validity period of the token. Base on this timestamp
-      * the wallet can find the correct key for this token in 
token_authorities.
-      */
-      struct GNUNET_TIME_Timestamp valid_after;
+       * Index of the public key in the @a token_family_slug's token family
+       * ``keys`` array that this output token will have.
+       */
+      unsigned int key_index;
 
       /**
-      * Number of tokens of this type required. Defaults to one if the
-      * field is not provided.
-      */
+       * Number of tokens of this type required. Defaults to one if the
+       * field is not provided.
+       */
       unsigned int count;
+
     } token;
+
   } details;
+
 };
 
 /**
@@ -264,12 +265,12 @@ struct TALER_MerchantContractTokenFamilyKey
   struct TALER_TokenIssuePublicKey pub;
 
   /**
-   * Tokens signed by this key will be valid after this time.
+   * Start time of the token family duration.
    */
   struct GNUNET_TIME_Timestamp valid_after;
 
   /**
-   * Tokens signed by this key will be valid before this time.
+   * Tokens signed by this key will be valid until this time.
    */
   struct GNUNET_TIME_Timestamp valid_before;
 };
@@ -279,7 +280,7 @@ struct TALER_MerchantContractTokenFamily
   /**
    * Slug of the token family.
    */
-  const char *slug;
+  char *slug;
 
   /**
    * Human-readable name of the token family.
@@ -334,12 +335,12 @@ struct TALER_MerchantContractTokenFamily
        * this type if the respective contract says so). May contain "*" for
        * any domain or subdomain.
        */
-      const char **trusted_domains;
+      char **trusted_domains;
 
       /**
        * Length of the @e trusted_domains array.
        */
-      unsigned int trusted_domains_len;
+      size_t trusted_domains_len;
     } subscription;
 
     /**
@@ -356,7 +357,7 @@ struct TALER_MerchantContractTokenFamily
        * semantics (like get 20% discount for my competitors 30% discount
        * token).
        */
-      const char **expected_domains;
+      char **expected_domains;
 
       /**
        * Length of the @e expected_domains array.
@@ -594,29 +595,31 @@ TMH_serialize_contract_v1 (const struct 
TALER_MerchantContract *contract,
  * of contract choices.
  *
  * @param name name of the choices field in the JSON
- * @param[out] choices pointer to the first element of the array
- * @param[out] choices_len pointer to the length of the array
+ * @param[out] choices set to the first element of the array
+ * @param[out] choices_len set to the length of the @a choices array
  * @return spec for parsing a choices array
  */
 struct GNUNET_JSON_Specification
-TALER_JSON_spec_choices (const char *name,
-                         struct TALER_MerchantContractChoice **choices,
-                         unsigned int *choices_len);
+TALER_JSON_spec_choices (
+  const char *name,
+  struct TALER_MerchantContractChoice **choices,
+  unsigned int *choices_len);
+
 
 /**
  * Provide specification to parse given JSON object to an array
  * of token families.
  *
  * @param name name of the token families field in the JSON
- * @param[out] families pointer to the first element of the array
- * @param[out] families_len pointer to the length of the array
+ * @param[out] families set to the first element of the array
+ * @param[out] families_len set to the length of the @a families
  * @return spec for parsing a token families object
  */
 struct GNUNET_JSON_Specification
-TALER_JSON_spec_token_families (const char *name,
-                                struct TALER_MerchantContractTokenFamily **
-                                families,
-                                unsigned int *families_len);
+TALER_JSON_spec_token_families (
+  const char *name,
+  struct TALER_MerchantContractTokenFamily **families,
+  unsigned int *families_len);
 
 
 /**
@@ -624,13 +627,14 @@ TALER_JSON_spec_token_families (const char *name,
  * @a valid_after to find the matching public key within it.
  *
  * @param slug slug of the token family
- * @param valid_after start time of the validity period of the key
+ * @param valid_after end time of the validity period of the key to find
  * @param families array of token families to search in
  * @param families_len length of the @a families array
  * @param[out] family found family, set to NULL to only check for existence
  * @param[out] key found key, set to NULL to only check for existence
  * @return #GNUNET_OK on success #GNUNET_NO if no key was found
  */
+// FIXME: awkward API, implementation duplicates code...
 enum GNUNET_GenericReturnValue
 TMH_find_token_family_key (
   const char *slug,
@@ -639,3 +643,5 @@ TMH_find_token_family_key (
   unsigned int families_len,
   struct TALER_MerchantContractTokenFamily *family,
   struct TALER_MerchantContractTokenFamilyKey *key);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 640731e6..5dece35d 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -2454,38 +2454,61 @@ phase_execute_pay_transaction (struct PayContext *pc)
 /**
  * Ensures that the expected number of tokens for a @e key
  * are provided as inputs and have valid signatures.
+ *
+ * @param[in,out] pc payment context we are processing
+ * @param family family the tokens should be from
+ * @param index number of the input we are handling
+ * @param expected_num number of tokens expected
+ * @return #GNUNET_YES on success
  */
 static enum GNUNET_GenericReturnValue
-find_valid_input_tokens (struct PayContext *pc,
-                         struct TALER_MerchantContractTokenFamilyKey *key,
-                         unsigned int index,
-                         unsigned int expected_num)
+find_valid_input_tokens (
+  struct PayContext *pc,
+  const struct TALER_MerchantContractTokenFamily *family,
+  unsigned int index,
+  unsigned int expected_num)
 {
   unsigned int num_validated = 0;
-  struct TokenUseConfirmation *tuc = NULL;
+  struct GNUNET_TIME_Timestamp now
+    = GNUNET_TIME_timestamp_get ();
 
   for (unsigned int j = 0; j < expected_num; j++)
   {
-    tuc = &pc->tokens[index + j];
+    struct TokenUseConfirmation *tuc = &pc->tokens[index + j];
+    const struct TALER_MerchantContractTokenFamilyKey *key = NULL;
 
-    if (NULL == tuc)
+    for (unsigned int i=0; i<family->keys_len; i++)
+    {
+      const struct TALER_MerchantContractTokenFamilyKey *ki
+        = &family->keys[i];
+
+      if (GNUNET_TIME_timestamp_cmp (ki->valid_after, >, now) ||
+          GNUNET_TIME_timestamp_cmp (ki->valid_before, <, now))
+      {
+        continue; /* ki currently not valid */
+      }
+      // FIXME: tuc->h_issue.hash is NOT initialized here!
+      if (0 ==
+          GNUNET_memcmp (&ki->pub.public_key->pub_key_hash,
+                         &tuc->h_issue.hash))
+      {
+        key = ki;
+        break;
+      }
+    }
+    if (NULL == key)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Input token for public key with "
-                  "valid_after `%s' not found\n",
-                  GNUNET_TIME_timestamp2s (key->valid_after));
+                  "Input token supplied for public key that is not 
acceptable\n");
       GNUNET_break (0);
       pay_end (pc,
                TALER_MHD_reply_with_error (
                  pc->connection,
                  MHD_HTTP_BAD_REQUEST,
-                 TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                 "'tokens' array is missing required input token"));
+                 TALER_EC_MERCHANT_GENERIC_TOKEN_KEY_UNKNOWN,
+                 NULL));
       return GNUNET_NO;
     }
-
-    tuc->h_issue.hash = key->pub.public_key->pub_key_hash;
-
     if (GNUNET_OK !=
         TALER_token_issue_verify (&tuc->pub,
                                   &key->pub,
@@ -2512,9 +2535,9 @@ find_valid_input_tokens (struct PayContext *pc,
                                        &tuc->sig))
     {
       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Input token for public key with valid_after "
+                  "Input token for public key with valid_before "
                   "`%s' has invalid use signature\n",
-                  GNUNET_TIME_timestamp2s (key->valid_after));
+                  GNUNET_TIME_timestamp2s (key->valid_before));
       GNUNET_break (0);
       pay_end (pc,
                TALER_MHD_reply_with_error (
@@ -2531,10 +2554,9 @@ find_valid_input_tokens (struct PayContext *pc,
   if (num_validated != expected_num)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Expected %d tokens for public key with valid_after "
-                "`%s', but found %d\n",
+                "Expected %d tokens for family %s, but found %d\n",
                 expected_num,
-                GNUNET_TIME_timestamp2s (key->valid_after),
+                family->slug,
                 num_validated);
     GNUNET_break (0);
     pay_end (pc,
@@ -2615,6 +2637,34 @@ sign_token_envelopes (struct PayContext *pc,
 }
 
 
+/**
+ * Find the family entry for the family of the given @a slug
+ * in @a pc.
+ *
+ * @param[in] pc payment context to search
+ * @param slug slug to search for
+ * @return NULL if @a slug was not found
+ */
+static struct TALER_MerchantContractTokenFamily *
+find_family (const struct PayContext *pc,
+             const char *slug)
+{
+  for (unsigned int i = 0; i<pc->token_families_len; i++)
+  {
+    if (0 == strcmp (pc->token_families[i].slug,
+                     slug))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Token family %s found with %u keys\n",
+                  slug,
+                  pc->token_families[i].keys_len);
+      return &pc->token_families[i];
+    }
+  }
+  return NULL;
+}
+
+
 /**
  * Validate tokens and token envelopes. First, we check if all tokens listed
  * in the 'inputs' array of the selected choice are present in the 'tokens'
@@ -2668,33 +2718,24 @@ phase_validate_tokens (struct PayContext *pc)
   }
 
   {
-    struct TALER_MerchantContractChoice selected;
-    struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
-
-    selected = pc->choices[pc->choice_index];
+    const struct TALER_MerchantContractChoice *selected
+      = &pc->choices[pc->choice_index];
 
-    for (unsigned int i = 0; i<selected.inputs_len; i++)
+    for (unsigned int i = 0; i<selected->inputs_len; i++)
     {
-      struct TALER_MerchantContractInput input = selected.inputs[i];
-      struct TALER_MerchantContractTokenFamily family;
-      struct TALER_MerchantContractTokenFamilyKey key;
+      const struct TALER_MerchantContractInput *input
+        = &selected->inputs[i];
+      const struct TALER_MerchantContractTokenFamily *family;
 
-      if (input.type != TALER_MCIT_TOKEN)
+      if (input->type != TALER_MCIT_TOKEN)
       {
         /* only validate inputs of type token (for now) */
         continue;
       }
 
-      /* TODO: Replace this with ordering convention. */
-      if (GNUNET_OK !=
-          TMH_find_token_family_key (input.details.token.
-                                     token_family_slug,
-                                     input.details.token.
-                                     valid_after,
-                                     pc->token_families,
-                                     pc->token_families_len,
-                                     &family,
-                                     &key))
+      family = find_family (pc,
+                            input->details.token.token_family_slug);
+      if (NULL == family)
       {
         /* this should never happen, since the choices and
            token families are validated on insert. */
@@ -2704,32 +2745,14 @@ phase_validate_tokens (struct PayContext *pc)
                    pc->connection,
                    MHD_HTTP_INTERNAL_SERVER_ERROR,
                    TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                   NULL));
-        return;
-      }
-
-      /* Ensure tokens signed by this key are valid at the current time. */
-      if (GNUNET_TIME_timestamp_cmp (key.valid_after, >, now) ||
-          GNUNET_TIME_timestamp_cmp (key.valid_before, <=, now))
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                    "Token family key validity period from %s to %s "
-                    "is not valid at the current time\n",
-                    GNUNET_TIME_timestamp2s (key.valid_after),
-                    GNUNET_TIME_timestamp2s (key.valid_before));
-        GNUNET_break (0);
-        pay_end (pc,
-                 TALER_MHD_reply_with_error (pc->connection,
-                                             MHD_HTTP_CONFLICT,
-                                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID,
-                                             NULL));
+                   "token family not found in order"));
         return;
       }
-
-      if (GNUNET_NO == find_valid_input_tokens (pc,
-                                                &key,
-                                                i,
-                                                input.details.token.count))
+      if (GNUNET_NO ==
+          find_valid_input_tokens (pc,
+                                   family,
+                                   i,
+                                   input->details.token.count))
       {
         /* Error is already scheduled from find_valid_input_token. */
         return;
@@ -2738,58 +2761,74 @@ phase_validate_tokens (struct PayContext *pc)
 
     GNUNET_array_grow (pc->output_tokens,
                        pc->output_tokens_len,
-                       selected.outputs_len);
+                       selected->outputs_len);
 
-    for (unsigned int i = 0; i<selected.outputs_len; i++)
+    for (unsigned int i = 0; i<selected->outputs_len; i++)
     {
       enum GNUNET_DB_QueryStatus qs;
       struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
-      struct TALER_MerchantContractOutput output = selected.outputs[i];
-      struct TALER_MerchantContractTokenFamily family;
-      struct TALER_MerchantContractTokenFamilyKey key;
+      const struct TALER_MerchantContractOutput *output
+        = &selected->outputs[i];
+      struct TALER_MerchantContractTokenFamily *family;
+      struct TALER_MerchantContractTokenFamilyKey *key;
 
-      if (output.type != TALER_MCOT_TOKEN)
+      if (output->type != TALER_MCOT_TOKEN)
       {
         /* only validate outputs of type tokens (for now) */
         continue;
       }
 
-      if (GNUNET_OK !=
-          TMH_find_token_family_key (output.details.token.
-                                     token_family_slug,
-                                     output.details.token.
-                                     valid_after,
-                                     pc->token_families,
-                                     pc->token_families_len,
-                                     &family,
-                                     &key))
+      family = find_family (pc,
+                            output->details.token.token_family_slug);
+      if (NULL == family)
       {
         /* this should never happen, since the choices and
            token families are validated on insert. */
         GNUNET_break (0);
         pay_end (pc,
-                 TALER_MHD_reply_with_error (pc->connection,
-                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                             
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                                             NULL));
+                 TALER_MHD_reply_with_error (
+                   pc->connection,
+                   MHD_HTTP_INTERNAL_SERVER_ERROR,
+                   TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                   "token family not found in order"));
         return;
       }
-
-      qs = TMH_db->lookup_token_family_key (TMH_db->cls,
-                                            pc->hc->instance->settings.id,
-                                            family.slug,
-                                            key.valid_after,
-                                            key.valid_after,
-                                            &details);
-
+      if (output->details.token.key_index >= family->keys_len)
+      {
+        /* this should never happen, since the choices and
+           token families are validated on insert. */
+        GNUNET_break (0);
+        pay_end (pc,
+                 TALER_MHD_reply_with_error (
+                   pc->connection,
+                   MHD_HTTP_INTERNAL_SERVER_ERROR,
+                   TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                   "key index invalid for token family"));
+        return;
+      }
+      key = &family->keys[output->details.token.key_index];
+      qs = TMH_db->lookup_token_family_key (
+        TMH_db->cls,
+        pc->hc->instance->settings.id,
+        family->slug,
+        pc->timestamp,
+        pc->pay_deadline,
+        &details);
       if (qs <= 0)
       {
+        GNUNET_log (
+          GNUNET_ERROR_TYPE_ERROR,
+          "Did not find key for %s at [%llu,%llu]\n",
+          family->slug,
+          (unsigned long long) pc->timestamp.abs_time.abs_value_us,
+          (unsigned long long) pc->pay_deadline.abs_time.abs_value_us);
         GNUNET_break (0);
         pay_end (pc,
-                 TALER_MHD_reply_with_error (pc->connection,
-                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                             NULL));
+                 TALER_MHD_reply_with_error (
+                   pc->connection,
+                   MHD_HTTP_INTERNAL_SERVER_ERROR,
+                   TALER_EC_GENERIC_DB_FETCH_FAILED,
+                   NULL));
         return;
       }
 
@@ -2797,13 +2836,13 @@ phase_validate_tokens (struct PayContext *pc)
 
       if (GNUNET_OK !=
           sign_token_envelopes (pc,
-                                &key,
+                                key,
                                 &details.priv,
                                 /* TODO: Use critical field stored in database 
here instead. */
                                 details.token_family.kind ==
                                 TALER_MERCHANTDB_TFK_Subscription,
                                 i,
-                                output.details.token.count))
+                                output->details.token.count))
       {
         /* Error is already scheduled from sign_token_envelopes. */
         return;
@@ -3632,6 +3671,8 @@ phase_parse_pay (struct PayContext *pc)
                                      &tuc->sig),
         GNUNET_JSON_spec_fixed_auto ("token_pub",
                                      &tuc->pub),
+        GNUNET_JSON_spec_fixed_auto ("h_issue",
+                                     &tuc->h_issue),
         TALER_JSON_spec_token_issue_sig ("ub_sig",
                                          &tuc->unblinded_sig),
         GNUNET_JSON_spec_end ()
@@ -3710,6 +3751,25 @@ pay_context_cleanup (void *cls)
     GNUNET_free (eg);
   }
   GNUNET_free (pc->egs);
+  for (unsigned int i = 0; i< pc->token_families_len; i++)
+  {
+    struct TALER_MerchantContractTokenFamily *tf
+      = &pc->token_families[i];
+
+    GNUNET_free (tf->slug);
+    GNUNET_free (tf->name);
+    GNUNET_free (tf->description);
+    json_decref (tf->description_i18n);
+    for (unsigned int j = 0; j<tf->keys_len; j++)
+    {
+      struct TALER_MerchantContractTokenFamilyKey *key
+        = &tf->keys[j];
+
+      TALER_token_issue_pub_free (&key->pub);
+    }
+    GNUNET_free (tf->keys);
+  }
+  GNUNET_free (pc->token_families);
   if (NULL != pc->response)
   {
     MHD_destroy_response (pc->response);
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c 
b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
index c2d3a58c..5fb2c84f 100644
--- a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
+++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2023 Taler Systems SA
+  (C) 2023, 2024 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free 
Software
@@ -54,11 +54,11 @@ TMH_private_get_tokenfamilies_SLUG (const struct 
TMH_RequestHandler *rh,
                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                                        "lookup_token_family");
   }
-  if (0 == status)
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == status)
   {
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_NOT_FOUND,
-                                       
TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
+                                       
TALER_EC_MERCHANT_GENERIC_TOKEN_FAMILY_UNKNOWN,
                                        hc->infix);
   }
   {
@@ -85,25 +85,42 @@ TMH_private_get_tokenfamilies_SLUG (const struct 
TMH_RequestHandler *rh,
     result = TALER_MHD_REPLY_JSON_PACK (
       connection,
       MHD_HTTP_OK,
-      GNUNET_JSON_pack_string ("slug", details.slug),
-      GNUNET_JSON_pack_string ("name", details.name),
-      GNUNET_JSON_pack_string ("description", details.description),
+      GNUNET_JSON_pack_string ("slug",
+                               details.slug),
+      GNUNET_JSON_pack_string ("name",
+                               details.name),
+      GNUNET_JSON_pack_string ("description",
+                               details.description),
       GNUNET_JSON_pack_object_steal ("description_i18n",
                                      details.description_i18n),
-      GNUNET_JSON_pack_timestamp ("valid_after", details.valid_after),
-      GNUNET_JSON_pack_timestamp ("valid_before", details.valid_before),
-      GNUNET_JSON_pack_time_rel ("duration", details.duration),
-      GNUNET_JSON_pack_string ("kind", kind),
-      GNUNET_JSON_pack_int64 ("issued", details.issued),
-      GNUNET_JSON_pack_int64 ("used", details.used)
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_object_steal ("extra_data",
+                                       details.extra_data)),
+      GNUNET_JSON_pack_timestamp ("valid_after",
+                                  details.valid_after),
+      GNUNET_JSON_pack_timestamp ("valid_before",
+                                  details.valid_before),
+      GNUNET_JSON_pack_time_rel ("duration",
+                                 details.duration),
+      GNUNET_JSON_pack_time_rel ("validity_granularity",
+                                 details.validity_granularity),
+      GNUNET_JSON_pack_time_rel ("start_offset",
+                                 details.start_offset),
+      GNUNET_JSON_pack_string ("kind",
+                               kind),
+      GNUNET_JSON_pack_int64 ("issued",
+                              details.issued),
+      GNUNET_JSON_pack_int64 ("used",
+                              details.used)
       );
 
     GNUNET_free (details.name);
     GNUNET_free (details.description);
+    GNUNET_free (details.cipher_spec);
     GNUNET_free (kind);
     return result;
   }
 }
 
 
-/* end of taler-merchant-httpd_private-get-products-SLUG.c */
+/* end of taler-merchant-httpd_private-get-token-families-SLUG.c */
diff --git 
a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c 
b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
index 56235514..80547e02 100644
--- a/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
+++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2023 Taler Systems SA
+  (C) 2023, 2024 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as
@@ -33,6 +33,7 @@
  */
 #define MAX_RETRIES 3
 
+
 /**
  * Handle a PATCH "/tokenfamilies/$slug" request.
  *
@@ -49,7 +50,6 @@ TMH_private_patch_token_family_SLUG (const struct 
TMH_RequestHandler *rh,
   struct TMH_MerchantInstance *mi = hc->instance;
   const char *slug = hc->infix;
   struct TALER_MERCHANTDB_TokenFamilyDetails details = {0};
-  enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_string ("name",
                              (const char **) &details.name),
@@ -59,15 +59,16 @@ TMH_private_patch_token_family_SLUG (const struct 
TMH_RequestHandler *rh,
       GNUNET_JSON_spec_json ("description_i18n",
                              &details.description_i18n),
       NULL),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_json ("extra_data",
+                             &details.extra_data),
+      NULL),
     GNUNET_JSON_spec_timestamp ("valid_after",
                                 &details.valid_after),
     GNUNET_JSON_spec_timestamp ("valid_before",
                                 &details.valid_before),
-    GNUNET_JSON_spec_relative_time ("duration",
-                                    &details.duration),
     GNUNET_JSON_spec_end ()
   };
-  struct GNUNET_TIME_Relative validity;
 
   GNUNET_assert (NULL != mi);
   GNUNET_assert (NULL != slug);
@@ -83,19 +84,17 @@ TMH_private_patch_token_family_SLUG (const struct 
TMH_RequestHandler *rh,
              : MHD_NO;
   }
 
-  validity = GNUNET_TIME_absolute_get_difference (
-    details.valid_after.abs_time,
-    details.valid_before.abs_time);
-
-  /* Check if start_time is before valid_before */
-  if (GNUNET_TIME_relative_is_zero (validity))
+  /* Ensure that valid_after is before valid_before */
+  if (GNUNET_TIME_timestamp_cmp (details.valid_after,
+                                 >=,
+                                 details.valid_before))
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_BAD_REQUEST,
                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                       "invalid_validity_duration");
+                                       "valid_after >= valid_before");
   }
 
   if (NULL == details.description_i18n)
@@ -113,13 +112,14 @@ TMH_private_patch_token_family_SLUG (const struct 
TMH_RequestHandler *rh,
                                        "description_i18n");
   }
 
-  qs = TMH_db->update_token_family (TMH_db->cls,
-                                    mi->settings.id,
-                                    slug,
-                                    &details);
   {
+    enum GNUNET_DB_QueryStatus qs;
     MHD_RESULT ret = MHD_NO;
 
+    qs = TMH_db->update_token_family (TMH_db->cls,
+                                      mi->settings.id,
+                                      slug,
+                                      &details);
     switch (qs)
     {
     case GNUNET_DB_STATUS_HARD_ERROR:
diff --git a/src/backend/taler-merchant-httpd_private-post-instances.c 
b/src/backend/taler-merchant-httpd_private-post-instances.c
index f56e7098..2bd5034d 100644
--- a/src/backend/taler-merchant-httpd_private-post-instances.c
+++ b/src/backend/taler-merchant-httpd_private-post-instances.c
@@ -293,6 +293,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler 
*rh,
       {
         mi->rc = 1;
         TMH_instance_decref (mi);
+        GNUNET_JSON_parse_free (spec);
         return TALER_MHD_reply_with_error (connection,
                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
                                            TALER_EC_GENERIC_DB_START_FAILED,
@@ -317,6 +318,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler 
*rh,
                                             is.id);
           mi->rc = 1;
           TMH_instance_decref (mi);
+          GNUNET_JSON_parse_free (spec);
           return ret;
         }
       case GNUNET_DB_STATUS_SOFT_ERROR:
@@ -333,6 +335,7 @@ TMH_private_post_instances (const struct TMH_RequestHandler 
*rh,
                                             is.id);
           mi->rc = 1;
           TMH_instance_decref (mi);
+          GNUNET_JSON_parse_free (spec);
           return ret;
         }
       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
@@ -350,6 +353,7 @@ retry:
     {
       mi->rc = 1;
       TMH_instance_decref (mi);
+      GNUNET_JSON_parse_free (spec);
       return TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                                          TALER_EC_GENERIC_DB_COMMIT_FAILED,
@@ -367,6 +371,7 @@ retry:
                                        created */
   }
 
+  GNUNET_JSON_parse_free (spec);
   return TALER_MHD_reply_static (connection,
                                  MHD_HTTP_NO_CONTENT,
                                  NULL,
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c 
b/src/backend/taler-merchant-httpd_private-post-orders.c
index 4efc1057..3aa108fd 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -255,22 +255,22 @@ struct OrderContext
     /**
      * Our order ID.
      */
-    const char *order_id;
+    char *order_id;
 
     /**
-    * Summary of the contract.
-    */
+     * Summary of the contract.
+     */
     const char *summary;
 
     /**
-    * Internationalized summary.
-    */
-    json_t *summary_i18n;
+     * Internationalized summary.
+     */
+    const json_t *summary_i18n;
 
     /**
-    * URL that will show that the contract 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;
 
     /**
@@ -280,9 +280,9 @@ struct OrderContext
     const char *fulfillment_message;
 
     /**
-    * Map from IETF BCP 47 language tags to localized fulfillment messages.
-    */
-    json_t *fulfillment_message_i18n;
+     * Map from IETF BCP 47 language tags to localized fulfillment messages.
+     */
+    const json_t *fulfillment_message_i18n;
 
     /**
     * Array of products that are part of the purchase.
@@ -332,7 +332,7 @@ struct OrderContext
     /**
      * Delivery location.
      */
-    json_t *delivery_location;
+    const json_t *delivery_location;
 
     /**
     * Gross amount value of the contract. Used to
@@ -693,21 +693,6 @@ clean_order (void *cls)
     json_decref (oc->set_exchanges.exchanges);
     oc->set_exchanges.exchanges = NULL;
   }
-  if (NULL != oc->parse_order.fulfillment_message_i18n)
-  {
-    json_decref (oc->parse_order.fulfillment_message_i18n);
-    oc->parse_order.fulfillment_message_i18n = NULL;
-  }
-  if (NULL != oc->parse_order.summary_i18n)
-  {
-    json_decref (oc->parse_order.summary_i18n);
-    oc->parse_order.summary_i18n = NULL;
-  }
-  if (NULL != oc->parse_order.delivery_location)
-  {
-    json_decref (oc->parse_order.delivery_location);
-    oc->parse_order.delivery_location = NULL;
-  }
   if (NULL != oc->merge_inventory.products)
   {
     json_decref (oc->merge_inventory.products);
@@ -727,17 +712,32 @@ clean_order (void *cls)
                      0);
   for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
   {
-    GNUNET_free (oc->parse_choices.token_families[i].name);
-    GNUNET_free (oc->parse_choices.token_families[i].description);
-    json_decref (oc->parse_choices.token_families[i].description_i18n);
-    for (unsigned int j = 0; j<oc->parse_choices.token_families[i].keys_len; 
j++
-         )
+    struct TALER_MerchantContractTokenFamily *mctf
+      = &oc->parse_choices.token_families[i];
+
+    GNUNET_free (mctf->slug);
+    GNUNET_free (mctf->name);
+    GNUNET_free (mctf->description);
+    json_decref (mctf->description_i18n);
+    switch (mctf->kind)
+    {
+    case TALER_MCTK_SUBSCRIPTION:
+      for (size_t j = 0; j<mctf->details.subscription.trusted_domains_len; j++)
+        GNUNET_free (mctf->details.subscription.trusted_domains[j]);
+      GNUNET_free (mctf->details.subscription.trusted_domains);
+      break;
+    case TALER_MCTK_DISCOUNT:
+      for (size_t j = 0; j<mctf->details.discount.expected_domains_len; j++)
+        GNUNET_free (mctf->details.discount.expected_domains[j]);
+      GNUNET_free (mctf->details.discount.expected_domains);
+      break;
+    }
+    for (unsigned int j = 0; j<mctf->keys_len; j++)
     {
-      GNUNET_CRYPTO_blind_sign_pub_decref (oc->parse_choices.token_families[i].
-                                           keys[j].pub.public_key);
+      GNUNET_CRYPTO_blind_sign_pub_decref (mctf->keys[j].pub.public_key);
     }
-    GNUNET_array_grow (oc->parse_choices.token_families[i].keys,
-                       oc->parse_choices.token_families[i].keys_len,
+    GNUNET_array_grow (mctf->keys,
+                       mctf->keys_len,
                        0);
   }
   GNUNET_array_grow (oc->parse_choices.token_families,
@@ -751,6 +751,7 @@ clean_order (void *cls)
                      0);
   json_decref (oc->parse_request.order);
   json_decref (oc->serialize_order.contract);
+  GNUNET_free (oc->parse_order.order_id);
   GNUNET_free (oc->parse_order.merchant_base_url);
   GNUNET_free (oc);
 }
@@ -1202,376 +1203,589 @@ salt_forgettable (struct OrderContext *oc)
 
 /**
  * Get rounded time interval. @a start is calculated by rounding
- * @a ts down to the nearest multiple of @a precision. @a end is
- * the next higher multiple of @a precision.
+ * @a ts down to the nearest multiple of @a precision.
  *
  * @param precision rounding precision.
  *        year, month, day, hour, minute are supported.
  * @param ts timestamp to round
  * @param[out] start start of the interval
- * @param[out] end end of the interval
  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
  */
 static enum GNUNET_GenericReturnValue
 get_rounded_time_interval (struct GNUNET_TIME_Relative precision,
                            struct GNUNET_TIME_Timestamp ts,
-                           struct GNUNET_TIME_Timestamp *start,
-                           struct GNUNET_TIME_Timestamp *end)
+                           struct GNUNET_TIME_Timestamp *start)
 {
-  struct tm*timeinfo;
+  struct tm timeinfo;
   time_t seconds;
 
   seconds = GNUNET_TIME_timestamp_to_s (ts);
-  timeinfo = localtime (&seconds);
+  GNUNET_break (NULL !=
+                localtime_r (&seconds,
+                             &timeinfo));
 
-  if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision))
+  if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS,
+                                ==,
+                                precision))
   {
-    timeinfo->tm_mon = 0;
-    timeinfo->tm_mday = 1;
-    timeinfo->tm_hour = 0;
-    timeinfo->tm_min = 0;
-    timeinfo->tm_sec = 0;
+    timeinfo.tm_mon = 0;
+    timeinfo.tm_mday = 1;
+    timeinfo.tm_hour = 0;
+    timeinfo.tm_min = 0;
+    timeinfo.tm_sec = 0;
   }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision))
+  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS,
+                                     ==,
+                                     precision))
   {
-    timeinfo->tm_mday = 1;
-    timeinfo->tm_hour = 0;
-    timeinfo->tm_min = 0;
-    timeinfo->tm_sec = 0;
+    timeinfo.tm_mday = 1;
+    timeinfo.tm_hour = 0;
+    timeinfo.tm_min = 0;
+    timeinfo.tm_sec = 0;
   }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision))
+  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS,
+                                     ==,
+                                     precision))
   {
-    timeinfo->tm_hour = 0;
-    timeinfo->tm_min = 0;
-    timeinfo->tm_sec = 0;
+    timeinfo.tm_hour = 0;
+    timeinfo.tm_min = 0;
+    timeinfo.tm_sec = 0;
   }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
+  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS,
+                                     ==,
+                                     precision))
   {
-    timeinfo->tm_min = 0;
-    timeinfo->tm_sec = 0;
+    timeinfo.tm_min = 0;
+    timeinfo.tm_sec = 0;
   }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
+  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES,
+                                     ==,
+                                     precision))
   {
-    timeinfo->tm_sec = 0;
+    timeinfo.tm_sec = 0;
   }
   else
   {
     return GNUNET_SYSERR;
   }
+  seconds = mktime (&timeinfo);
+  GNUNET_break (seconds != (time_t) -1);
+  *start = GNUNET_TIME_timestamp_from_s (seconds);
+  return GNUNET_OK;
+}
 
-  *start = GNUNET_TIME_timestamp_from_s (mktime (timeinfo));
 
-  if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision))
-  {
-    timeinfo->tm_year++;
-  }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision))
-  {
-    timeinfo->tm_mon = (timeinfo->tm_mon + 1) % 12;
-  }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision))
+/**
+ * Find the family entry for the family of the given @a slug
+ * in @a oc.
+ *
+ * @param[in] oc order context to search
+ * @param slug slug to search for
+ * @return NULL if @a slug was not found
+ */
+static struct TALER_MerchantContractTokenFamily *
+find_family (const struct OrderContext *oc,
+             const char *slug)
+{
+  for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
   {
-    timeinfo->tm_mday++;
+    if (0 == strcmp (oc->parse_choices.token_families[i].slug,
+                     slug))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Token family %s already in order\n",
+                  slug);
+      return &oc->parse_choices.token_families[i];
+    }
   }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
-  {
-    timeinfo->tm_hour++;
+  return NULL;
+}
+
+
+/**
+ * Function called with each applicable family key that should
+ * be added to the respective token family of the order.
+ *
+ * @param cls a `struct OrderContext *` to expand
+ * @param tfkd token family key details to add to the contract
+ */
+static void
+add_family_key (void *cls,
+                const struct TALER_MERCHANTDB_TokenFamilyKeyDetails *tfkd)
+{
+  struct OrderContext *oc = cls;
+  const struct TALER_MERCHANTDB_TokenFamilyDetails *tf = &tfkd->token_family;
+  struct TALER_MerchantContractTokenFamily *family;
+
+  family = find_family (oc,
+                        tf->slug);
+  if (NULL == family)
+  {
+    /* Family not yet in our contract terms, create new entry */
+    struct TALER_MerchantContractTokenFamily new_family = {
+      .slug = GNUNET_strdup (tf->slug),
+      .name = GNUNET_strdup (tf->name),
+      .description = GNUNET_strdup (tf->description),
+      .description_i18n = json_incref (tf->description_i18n),
+    };
+
+    switch (tf->kind)
+    {
+    case TALER_MERCHANTDB_TFK_Subscription:
+      {
+        json_t *tdomains = json_object_get (tf->extra_data,
+                                            "trusted_domains");
+        json_t *dom;
+        size_t i;
+
+        new_family.kind = TALER_MCTK_SUBSCRIPTION;
+        new_family.critical = true;
+        new_family.details.subscription.trusted_domains_len
+          = json_array_size (tdomains);
+        GNUNET_assert (new_family.details.subscription.trusted_domains_len
+                       < UINT_MAX);
+        new_family.details.subscription.trusted_domains
+          = GNUNET_new_array (
+              new_family.details.subscription.trusted_domains_len,
+              char *);
+        json_array_foreach (tdomains, i, dom)
+        {
+          const char *val;
+
+          val = json_string_value (dom);
+          GNUNET_break (NULL != val);
+          if (NULL != val)
+            new_family.details.subscription.trusted_domains[i]
+              = GNUNET_strdup (val);
+        }
+        break;
+      }
+    case TALER_MERCHANTDB_TFK_Discount:
+      {
+        json_t *edomains = json_object_get (tf->extra_data,
+                                            "expected_domains");
+        json_t *dom;
+        size_t i;
+
+        new_family.kind = TALER_MCTK_DISCOUNT;
+        new_family.critical = false;
+        new_family.details.discount.expected_domains_len
+          = json_array_size (edomains);
+        GNUNET_assert (new_family.details.discount.expected_domains_len
+                       < UINT_MAX);
+        new_family.details.discount.expected_domains
+          = GNUNET_new_array (
+              new_family.details.discount.expected_domains_len,
+              char *);
+        json_array_foreach (edomains, i, dom)
+        {
+          const char *val;
+
+          val = json_string_value (dom);
+          GNUNET_break (NULL != val);
+          if (NULL != val)
+            new_family.details.discount.expected_domains[i]
+              = GNUNET_strdup (val);
+        }
+        break;
+      }
+    }
+    GNUNET_array_append (oc->parse_choices.token_families,
+                         oc->parse_choices.token_families_len,
+                         new_family);
+    family = &oc->parse_choices.token_families[
+      oc->parse_choices.token_families_len - 1];
   }
-  else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
+  if (NULL == tfkd->pub.public_key)
+    return;
+  for (unsigned int i = 0; i<family->keys_len; i++)
   {
-    timeinfo->tm_min++;
+    if (TALER_token_issue_pub_cmp (&family->keys[i].pub,
+                                   &tfkd->pub))
+    {
+      /* A matching key is already in the list. */
+      return;
+    }
   }
-  else
+
   {
-    return GNUNET_SYSERR;
-  }
+    struct TALER_MerchantContractTokenFamilyKey key;
 
-  *end = GNUNET_TIME_timestamp_from_s (mktime (timeinfo));
-  return GNUNET_OK;
+    TALER_token_issue_pub_copy (&key.pub,
+                                &tfkd->pub);
+    key.valid_after = tfkd->signature_validity_start;
+    key.valid_before = tfkd->signature_validity_end;
+    GNUNET_array_append (family->keys,
+                         family->keys_len,
+                         key);
+  }
 }
 
 
 /**
- * Check if the token family with the given @a slug is already present in
- * the list of token families for this order. If not, fetch its details and
- * add it to the list. Then check if there is a public key with a matching
- * @a valid_after field. If not, generate a new key pair and store it in the
- * database.
+ * Check if the token family with the given @a slug is already present in the
+ * list of token families for this order. If not, fetch its details and add it
+ * to the list.
  *
  * @param[in,out] oc order context
  * @param slug slug of the token family
- * @param[in,out] valid_after validity start date of the token,
-                  subject to rounding. Set to the rounded validity
-                  start date of the matching key.
  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
  */
 static enum GNUNET_GenericReturnValue
-set_token_family (struct OrderContext *oc,
-                  const char *slug,
-                  struct GNUNET_TIME_Timestamp *valid_after)
+add_input_token_family (struct OrderContext *oc,
+                        const char *slug)
 {
-  struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
-  struct TALER_MerchantContractTokenFamily *family = NULL;
+  struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+  struct GNUNET_TIME_Timestamp end = oc->parse_order.pay_deadline;
   enum GNUNET_DB_QueryStatus qs;
-  /* TODO: Implement rounding duration of token family and use this here. */
-  struct GNUNET_TIME_Relative precision = GNUNET_TIME_UNIT_MONTHS;
-  struct GNUNET_TIME_Timestamp min_valid_after;
-  struct GNUNET_TIME_Timestamp max_valid_after;
-
-  if (GNUNET_OK !=
-      get_rounded_time_interval (precision,
-                                 *valid_after,
-                                 &min_valid_after,
-                                 &max_valid_after))
-  {
+  enum TALER_ErrorCode ec;
+  unsigned int http_status;
+
+  qs = TMH_db->lookup_token_family_keys (TMH_db->cls,
+                                         oc->hc->instance->settings.id,
+                                         slug,
+                                         now,
+                                         end,
+                                         &add_family_key,
+                                         oc);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
     GNUNET_break (0);
-    reply_with_error (oc,
-                      MHD_HTTP_INTERNAL_SERVER_ERROR,
-                      TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                      "valid_after");
-    return GNUNET_SYSERR;
-  }
+    http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+    break;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    GNUNET_break (0);
+    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,
+                "Input token family slug %s unknown\n",
+                slug);
+    http_status = MHD_HTTP_NOT_FOUND;
+    ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
+    break;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    return GNUNET_OK;
+  }
+  reply_with_error (oc,
+                    http_status,
+                    ec,
+                    slug);
+  return GNUNET_SYSERR;
+}
 
-  for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
+
+/**
+ * Find the index of a key in the @a family that is valid at
+ * the time @a valid_at.
+ *
+ * @param family to search
+ * @param valid_at time when the key must be valid
+ * @param[out] key_index index to initialize
+ * @return #GNUNET_OK if a matching key was found
+ */
+static enum GNUNET_GenericReturnValue
+find_key_index (struct TALER_MerchantContractTokenFamily *family,
+                struct GNUNET_TIME_Timestamp valid_at,
+                unsigned int *key_index)
+{
+  for (unsigned int i = 0; i<family->keys_len; i++)
   {
-    if (0 == strcmp (oc->parse_choices.token_families[i].slug,
-                     slug))
+    if ( (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
+                                     <=,
+                                     valid_at)) &&
+         (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_before,
+                                     >=,
+                                     valid_at)) )
     {
-      family = &oc->parse_choices.token_families[i];
-      break;
+      /* The token family and a matching key already exist. */
+      *key_index = i;
+      return GNUNET_OK;
     }
   }
-  if (NULL != family)
+  return GNUNET_NO;
+}
+
+
+/**
+ * Create fresh key pair based on @a cipher_spec.
+ *
+ * @param cipher_spec which kind of key pair should we generate
+ * @param[out] priv set to new private key
+ * @param[out] pub set to new public key
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_key (const char *cipher_spec,
+            struct TALER_TokenIssuePrivateKey *priv,
+            struct TALER_TokenIssuePublicKey *pub)
+{
+  unsigned int len;
+  char dummy;
+
+  if (0 == strcmp ("cs",
+                   cipher_spec))
   {
-    for (unsigned int i = 0; i<family->keys_len; i++)
-    {
-      if (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
-                                     >=,
-                                     min_valid_after) &&
-          GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
-                                     <,
-                                     max_valid_after))
-      {
-        /* The token family and a matching key is already added. */
-        *valid_after = family->keys[i].valid_after;
-        return GNUNET_OK;
-      }
-    }
+    GNUNET_CRYPTO_blind_sign_keys_create (
+      &priv->private_key,
+      &pub->public_key,
+      GNUNET_CRYPTO_BSA_CS);
+    return GNUNET_OK;
   }
+  if (1 ==
+      sscanf (cipher_spec,
+              "rsa(%u)%c",
+              &len,
+              &dummy))
+  {
+    GNUNET_CRYPTO_blind_sign_keys_create (
+      &priv->private_key,
+      &pub->public_key,
+      GNUNET_CRYPTO_BSA_RSA,
+      len);
+    return GNUNET_OK;
+  }
+  return GNUNET_SYSERR;
+}
 
+
+/**
+ * Check if the token family with the given @a slug is already present in the
+ * list of token families for this order. If not, fetch its details and add it
+ * to the list. Also checks if there is a public key with that expires after
+ * the payment deadline.  If not, generates a new key pair and stores it in
+ * the database.
+ *
+ * @param[in,out] oc order context
+ * @param slug slug of the token family
+ * @param valid_at time when the token returned must be valid
+ * @param[out] key_index set to the index of the respective public
+ *    key in the @a slug's token family keys array.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+add_output_token_family (struct OrderContext *oc,
+                         const char *slug,
+                         struct GNUNET_TIME_Timestamp valid_at,
+                         unsigned int *key_index)
+{
+  struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
+  struct TALER_MerchantContractTokenFamily *family;
+  enum GNUNET_DB_QueryStatus qs;
+
+  family = find_family (oc,
+                        slug);
+  if ( (NULL != family) &&
+       (GNUNET_OK ==
+        find_key_index (family,
+                        valid_at,
+                        key_index)) )
+    return GNUNET_OK;
   qs = TMH_db->lookup_token_family_key (TMH_db->cls,
                                         oc->hc->instance->settings.id,
                                         slug,
-                                        min_valid_after,
-                                        max_valid_after,
+                                        valid_at,
+                                        oc->parse_order.pay_deadline,
                                         &key_details);
-  /* slug is not needed */
-  GNUNET_free (key_details.token_family.slug);
-
-  if (qs <= 0)
+  switch (qs)
   {
-    enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
-    unsigned int http_status = 0;
-
-    switch (qs)
-    {
-    case GNUNET_DB_STATUS_HARD_ERROR:
-      GNUNET_break (0);
-      http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
-      ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
-      break;
-    case GNUNET_DB_STATUS_SOFT_ERROR:
-      GNUNET_break (0);
-      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 %s unknown\n",
-                  slug);
-      http_status = MHD_HTTP_NOT_FOUND;
-      ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
-      break;
-    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
-      /* case listed to make compilers happy */
-      GNUNET_assert (0);
-    }
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    GNUNET_break (0);
+    reply_with_error (oc,
+                      MHD_HTTP_INTERNAL_SERVER_ERROR,
+                      TALER_EC_GENERIC_DB_FETCH_FAILED,
+                      "lookup_token_family_key");
+    return GNUNET_SYSERR;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    GNUNET_break (0);
     reply_with_error (oc,
-                      http_status,
-                      ec,
+                      MHD_HTTP_INTERNAL_SERVER_ERROR,
+                      TALER_EC_GENERIC_DB_SOFT_FAILURE,
+                      "lookup_token_family_key");
+    return GNUNET_SYSERR; /* FIXME: retry instead? */
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Output token family slug %s unknown\n",
+                slug);
+    reply_with_error (oc,
+                      MHD_HTTP_NOT_FOUND,
+                      
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
                       slug);
     return GNUNET_SYSERR;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    break;
   }
 
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Lookup of token family %s at %llu yielded %s\n",
+              slug,
+              (unsigned long long) valid_at.abs_time.abs_value_us,
+              NULL == key_details.pub.public_key ? "no key" : "a key");
+
+  if (NULL == family)
   {
-    struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+    add_family_key (oc,
+                    &key_details);
+    family = find_family (oc,
+                          slug);
+    GNUNET_assert (NULL != family);
+  }
+  /* we don't need the full family details anymore */
+  GNUNET_free (key_details.token_family.slug);
+  GNUNET_free (key_details.token_family.name);
+  GNUNET_free (key_details.token_family.description);
+  json_decref (key_details.token_family.description_i18n);
+  json_decref (key_details.token_family.extra_data);
+
+  if (NULL != key_details.pub.public_key)
+  {
+    /* lookup_token_family_key must have found a matching key,
+       and it must have been added. Find and use the index. */
+    GNUNET_CRYPTO_blind_sign_pub_decref (key_details.pub.public_key);
+    GNUNET_CRYPTO_blind_sign_priv_decref (key_details.priv.private_key);
+    GNUNET_free (key_details.token_family.cipher_spec);
+    GNUNET_assert (GNUNET_OK ==
+                   find_key_index (family,
+                                   valid_at,
+                                   key_index));
+    return GNUNET_OK;
+  }
 
-    /* Verify that the token family is valid right now. */
-    if (GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_after,
-                                   >,
-                                   now) ||
-        GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_before,
-                                   <=,
-                                   now))
+  /* No suitable key exists, create one! */
+  {
+    struct TALER_MerchantContractTokenFamilyKey key;
+    enum GNUNET_DB_QueryStatus iqs;
+    struct TALER_TokenIssuePrivateKey token_priv;
+    struct GNUNET_TIME_Timestamp key_expires;
+    struct GNUNET_TIME_Timestamp round_start;
+
+    if (GNUNET_OK !=
+        get_rounded_time_interval (
+          key_details.token_family.validity_granularity,
+          valid_at,
+          &round_start))
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Token family %s expired or not yet valid\n",
+      GNUNET_break (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unsupported validity granularity interval %s found in 
database for token family %s!\n",
+                  GNUNET_TIME_relative2s (
+                    key_details.token_family.validity_granularity,
+                    false),
                   slug);
+      GNUNET_free (key_details.token_family.cipher_spec);
       reply_with_error (oc,
-                        /* TODO: HTTP Status Code GONE would be more elegant,
-                                 but that is already used to indicate that a 
product is out of stock. */
-                        MHD_HTTP_CONFLICT,
-                        
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_NOT_VALID,
-                        slug);
+                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                        "get_rounded_time_interval failed");
       return GNUNET_SYSERR;
     }
-  }
-
-  {
-    struct TALER_MerchantContractTokenFamilyKey key;
-
-    if (NULL == family)
+    if (GNUNET_TIME_relative_cmp (
+          key_details.token_family.duration,
+          <,
+          GNUNET_TIME_relative_add (
+            key_details.token_family.validity_granularity,
+            key_details.token_family.start_offset)))
     {
-      struct TALER_MerchantContractTokenFamily new_family = {
-        .slug = slug,
-        .name = key_details.token_family.name,
-        .description = key_details.token_family.description,
-        .description_i18n = key_details.token_family.description_i18n,
-        .keys = GNUNET_new (struct TALER_MerchantContractTokenFamilyKey),
-        .keys_len = 0,
-      };
-
-      switch (key_details.token_family.kind)
-      {
-      case TALER_MERCHANTDB_TFK_Subscription:
-        new_family.kind = TALER_MCTK_SUBSCRIPTION;
-        new_family.critical = true;
-        // TODO: Set trusted domains
-        break;
-      case TALER_MERCHANTDB_TFK_Discount:
-        new_family.kind = TALER_MCTK_DISCOUNT;
-        new_family.critical = false;
-        // TODO: Set expected domains
-        break;
-      }
-
-      GNUNET_array_append (oc->parse_choices.token_families,
-                           oc->parse_choices.token_families_len,
-                           new_family);
-
-      family = &oc->parse_choices.token_families[oc->parse_choices.
-                                                 token_families_len - 1];
+      GNUNET_break (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Inconsistent duration %s found in database for token family 
%s (below validity granularity plus start_offset)!\n",
+                  GNUNET_TIME_relative2s (key_details.token_family.duration,
+                                          false),
+                  slug);
+      GNUNET_free (key_details.token_family.cipher_spec);
+      reply_with_error (oc,
+                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                        "duration, validty_granularity and start_offset 
inconsistent for token family");
+      return GNUNET_SYSERR;
     }
-
-    if (NULL == key_details.pub.public_key)
+    key.valid_after
+      = GNUNET_TIME_timestamp_max (
+          GNUNET_TIME_absolute_to_timestamp (
+            GNUNET_TIME_absolute_subtract (
+              round_start.abs_time,
+              key_details.token_family.start_offset)),
+          key_details.token_family.valid_after);
+    key.valid_before
+      = GNUNET_TIME_timestamp_min (
+          GNUNET_TIME_absolute_to_timestamp (
+            GNUNET_TIME_absolute_add (
+              key.valid_after.abs_time,
+              key_details.token_family.duration)),
+          key_details.token_family.valid_before);
+    GNUNET_assert (GNUNET_OK ==
+                   get_rounded_time_interval (
+                     key_details.token_family.validity_granularity,
+                     key.valid_before,
+                     &key_expires));
+    GNUNET_assert (GNUNET_TIME_timestamp_cmp (
+                     key_expires,
+                     !=,
+                     round_start));
+    if (GNUNET_OK !=
+        create_key (key_details.token_family.cipher_spec,
+                    &token_priv,
+                    &key.pub))
     {
-      /* There is no matching key for this token family yet. */
-      /* We have to generate a fresh key pair. */
-      /* If public key is NULL, private key must also be NULL */
-      enum GNUNET_DB_QueryStatus iqs;
-      struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
-      struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
-      struct GNUNET_TIME_Timestamp valid_before =
-        GNUNET_TIME_absolute_to_timestamp (
-          GNUNET_TIME_absolute_add (min_valid_after.abs_time,
-                                    key_details.token_family.duration));
-
-      GNUNET_assert (NULL == key_details.priv.private_key);
-      if (GNUNET_TIME_timestamp_cmp (min_valid_after,
-                                     <,
-                                     key_details.token_family.valid_after))
-      {
-        /* If key would start before validity of token family,
-           set valid_after of key to the value of the token family. */
-        min_valid_after = key_details.token_family.valid_after;
-      }
-
-      if (GNUNET_TIME_timestamp_cmp (valid_before,
-                                     >,
-                                     key_details.token_family.valid_before))
-      {
-        /* If key would end after validity of token family,
-           set valid_before of key to the value of the token family. */
-        valid_before = key_details.token_family.valid_before;
-      }
-
-      GNUNET_CRYPTO_blind_sign_keys_create (&priv,
-                                            &pub,
-                                            /* TODO: Make cipher and key 
length configurable */
-                                            GNUNET_CRYPTO_BSA_RSA,
-                                            4096);
-      {
-        struct TALER_TokenIssuePublicKey token_pub = {
-          .public_key = pub,
-        };
-        struct TALER_TokenIssuePrivateKey token_priv = {
-          .private_key = priv,
-        };
-
-        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                    "Storing new key for slug %s of %s\n",
-                    slug,
-                    oc->hc->instance->settings.id);
-        iqs = TMH_db->insert_token_family_key (TMH_db->cls,
-                                               oc->hc->instance->settings.id,
-                                               slug,
-                                               &token_pub,
-                                               &token_priv,
-                                               min_valid_after,
-                                               valid_before);
-        GNUNET_CRYPTO_blind_sign_priv_decref (priv);
-        if (iqs <= 0)
-        {
-          enum TALER_ErrorCode ec = 
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
-          unsigned int http_status = 0;
-
-          switch (iqs)
-          {
-          case GNUNET_DB_STATUS_HARD_ERROR:
-            GNUNET_break (0);
-            http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
-            ec = TALER_EC_GENERIC_DB_STORE_FAILED;
-            break;
-          case GNUNET_DB_STATUS_SOFT_ERROR:
-            GNUNET_break (0);
-            http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
-            ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
-            break;
-          case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
-            GNUNET_break (0);
-            http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
-            ec = TALER_EC_GENERIC_DB_STORE_FAILED;
-            break;
-          case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
-            /* case listed to make compilers happy */
-            GNUNET_assert (0);
-          }
-          reply_with_error (oc,
-                            http_status,
-                            ec,
-                            "token_family_slug");
-          return GNUNET_SYSERR;
-        }
-
-        key.pub = token_pub;
-        key.valid_after = min_valid_after;
-        key.valid_before = valid_before;
-      }
+      GNUNET_break (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unsupported cipher family %s found in database for token 
family %s!\n",
+                  key_details.token_family.cipher_spec,
+                  slug);
+      GNUNET_free (key_details.token_family.cipher_spec);
+      reply_with_error (oc,
+                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                        "invalid cipher stored in local database for token 
family");
+      return GNUNET_SYSERR;
     }
-    else
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Storing new key for slug %s of %s\n",
+                slug,
+                oc->hc->instance->settings.id);
+    iqs = TMH_db->insert_token_family_key (TMH_db->cls,
+                                           oc->hc->instance->settings.id,
+                                           slug,
+                                           &key.pub,
+                                           &token_priv,
+                                           key_expires,
+                                           key.valid_after,
+                                           key.valid_before);
+    GNUNET_CRYPTO_blind_sign_priv_decref (token_priv.private_key);
+    switch (iqs)
     {
-      key.pub = key_details.pub;
-      key.valid_after = key_details.valid_after;
-      key.valid_before = key_details.valid_before;
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      reply_with_error (oc,
+                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                        TALER_EC_GENERIC_DB_STORE_FAILED,
+                        NULL);
+      return GNUNET_SYSERR;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      reply_with_error (oc,
+                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
+                        NULL);
+      return GNUNET_SYSERR; // FIXME: or retry?
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      GNUNET_break (0);
+      reply_with_error (oc,
+                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                        TALER_EC_GENERIC_DB_STORE_FAILED,
+                        NULL);
+      return GNUNET_SYSERR;
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
     }
-
+    *key_index = family->keys_len;
     GNUNET_array_append (family->keys,
                          family->keys_len,
                          key);
-
-    *valid_after = key.valid_after;
   }
-
   return GNUNET_OK;
 }
 
@@ -1651,15 +1865,8 @@ serialize_order (struct OrderContext *oc)
         /* TODO: Remove h_pub. */
         GNUNET_JSON_pack_data_auto ("h_pub",
                                     &key.pub.public_key->pub_key_hash),
-        GNUNET_JSON_pack_allow_null (
-          GNUNET_JSON_pack_rsa_public_key ("rsa_pub",
-                                           key.pub.public_key->details.
-                                           rsa_public_key)),
-        // GNUNET_JSON_pack_allow_null(
-        //   GNUNET_JSON_pack_data_auto ("cs_pub",
-        //                               
&key.pub.public_key->details.cs_public_key)),
-        GNUNET_JSON_pack_int64 ("cipher",
-                                key.pub.public_key->cipher),
+        TALER_JSON_pack_token_pub ("public_key",
+                                   &key.pub),
         GNUNET_JSON_pack_timestamp ("valid_after",
                                     key.valid_after),
         GNUNET_JSON_pack_timestamp ("valid_before",
@@ -1715,9 +1922,7 @@ serialize_order (struct OrderContext *oc)
         GNUNET_JSON_pack_string ("token_family_slug",
                                  input->details.token.token_family_slug),
         GNUNET_JSON_pack_int64 ("number",
-                                input->details.token.count),
-        GNUNET_JSON_pack_timestamp ("valid_after",
-                                    input->details.token.valid_after)
+                                input->details.token.count)
         );
 
       GNUNET_assert (0 ==
@@ -1741,8 +1946,8 @@ serialize_order (struct OrderContext *oc)
                                  output->details.token.token_family_slug),
         GNUNET_JSON_pack_int64 ("number",
                                 output->details.token.count),
-        GNUNET_JSON_pack_timestamp ("valid_after",
-                                    output->details.token.valid_after)
+        GNUNET_JSON_pack_int64 ("key_index",
+                                output->details.token.key_index)
         );
 
       GNUNET_assert (0 ==
@@ -1772,7 +1977,7 @@ serialize_order (struct OrderContext *oc)
                              oc->parse_order.summary),
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_object_incref ("summary_i18n",
-                                      oc->parse_order.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)),
@@ -1781,7 +1986,8 @@ serialize_order (struct OrderContext *oc)
                                oc->parse_order.fulfillment_message)),
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n",
-                                      
oc->parse_order.fulfillment_message_i18n))
+                                      (json_t *) oc->parse_order.
+                                      fulfillment_message_i18n))
     ,
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_string ("fulfillment_url",
@@ -1808,7 +2014,8 @@ serialize_order (struct OrderContext *oc)
                                   oc->parse_order.delivery_date)),
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_object_incref ("delivery_location",
-                                      oc->parse_order.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",
@@ -2348,8 +2555,7 @@ parse_order (struct OrderContext *oc)
    * mostly because in GNUnet relative times can't
    * be negative.  */
   bool no_fee;
-  /* TODO: Move "amount" field to choices. This entails moving the
-           stefan-base fee calculation to the parse_choices phase. */
+  const char *oid;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_uint64 ("version",
@@ -2364,20 +2570,20 @@ parse_order (struct OrderContext *oc)
                                     &oc->parse_order.products),
       NULL),
     GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_json ("summary_i18n",
-                             &oc->parse_order.summary_i18n),
+      GNUNET_JSON_spec_object_const ("summary_i18n",
+                                     &oc->parse_order.summary_i18n),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("order_id",
-                               &oc->parse_order.order_id),
+                               &oid),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("fulfillment_message",
                                &oc->parse_order.fulfillment_message),
       NULL),
     GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_json ("fulfillment_message_i18n",
-                             &oc->parse_order.fulfillment_message_i18n),
+      GNUNET_JSON_spec_object_const ("fulfillment_message_i18n",
+                                     
&oc->parse_order.fulfillment_message_i18n),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("fulfillment_url",
@@ -2420,8 +2626,8 @@ parse_order (struct OrderContext *oc)
                                   &oc->parse_order.max_fee),
       &no_fee),
     GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_json ("delivery_location",
-                             &oc->parse_order.delivery_location),
+      GNUNET_JSON_spec_object_const ("delivery_location",
+                                     &oc->parse_order.delivery_location),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("delivery_date",
@@ -2521,7 +2727,11 @@ parse_order (struct OrderContext *oc)
   }
 
   /* Add order_id if it doesn't exist. */
-  if (NULL == oc->parse_order.order_id)
+  if (NULL != oid)
+  {
+    oc->parse_order.order_id = GNUNET_strdup (oid);
+  }
+  else
   {
     char buf[256];
     time_t timer;
@@ -2649,10 +2859,14 @@ parse_order (struct OrderContext *oc)
     }
   }
 
-  if (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time))
+  if ( (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time)) 
||
+       (GNUNET_TIME_absolute_is_never (oc->parse_order.pay_deadline.abs_time)) 
)
   {
     oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp (
       settings->default_pay_delay);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Pay deadline was zero (or never), setting to %s\n",
+                GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline));
   }
   else if (GNUNET_TIME_absolute_is_past 
(oc->parse_order.pay_deadline.abs_time))
   {
@@ -2664,7 +2878,9 @@ parse_order (struct OrderContext *oc)
       NULL);
     return;
   }
-
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Pay deadline is %s\n",
+              GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline));
   if ( (! GNUNET_TIME_absolute_is_zero (
           oc->parse_order.refund_deadline.abs_time)) &&
        (GNUNET_TIME_absolute_is_past (
@@ -2824,7 +3040,8 @@ parse_choices (struct OrderContext *oc)
     };
     enum GNUNET_GenericReturnValue ret;
 
-    ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i),
+    ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices,
+                                             i),
                              spec,
                              &error_name,
                              &error_line);
@@ -2841,7 +3058,8 @@ parse_choices (struct OrderContext *oc)
       return;
     }
 
-    if (0 == json_array_size (jinputs) && 0 == json_array_size (joutputs))
+    if ( (0 == json_array_size (jinputs)) &&
+         (0 == json_array_size (joutputs)) )
     {
       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                   "Choice #%u must have at least one input or output\n",
@@ -2859,20 +3077,17 @@ parse_choices (struct OrderContext *oc)
       size_t idx;
       json_array_foreach ((json_t *) jinputs, idx, jinput)
       {
-        struct TALER_MerchantContractInput input = {.details.token.count = 1};
+        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),
-          /* TODO: Remove valid_after field for inputs,
-                   use current system time instead. */
-          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),
@@ -2917,10 +3132,9 @@ parse_choices (struct OrderContext *oc)
           continue;
         }
 
-        if (GNUNET_OK != set_token_family (oc,
-                                           
input.details.token.token_family_slug
-                                           ,
-                                           &input.details.token.valid_after))
+        if (GNUNET_OK !=
+            add_input_token_family (oc,
+                                    input.details.token.token_family_slug))
         {
           /* error is already scheduled, return. */
           return;
@@ -2937,24 +3151,27 @@ parse_choices (struct OrderContext *oc)
       size_t idx;
       json_array_foreach ((json_t *) joutputs, idx, joutput)
       {
-        struct TALER_MerchantContractOutput output = { .details.token.count = 
1}
-        ;
+        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),
-          /* TODO: Make valid_after optional, default to current system time. 
*/
-          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_mark_optional (
+            GNUNET_JSON_spec_timestamp ("valid_at",
+                                        &valid_at),
+            &nots),
           GNUNET_JSON_spec_end ()
         };
 
@@ -2976,9 +3193,35 @@ parse_choices (struct OrderContext *oc)
                             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));
+        }
 
-        output.type = TMH_contract_output_type_from_string (kind);
 
+        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);
@@ -2999,10 +3242,11 @@ parse_choices (struct OrderContext *oc)
           continue;
         }
 
-        if (GNUNET_OK != set_token_family (oc,
-                                           output.details.token.
-                                           token_family_slug,
-                                           &output.details.token.valid_after))
+        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;
diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.c 
b/src/backend/taler-merchant-httpd_private-post-token-families.c
index 1fbbc44e..b9100522 100644
--- a/src/backend/taler-merchant-httpd_private-post-token-families.c
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.c
@@ -54,6 +54,9 @@ token_families_equal (const struct 
TALER_MERCHANTDB_TokenFamilyDetails *tf1,
                          tf2->description)) &&
            (1 == json_equal (tf1->description_i18n,
                              tf2->description_i18n)) &&
+           ( (tf1->extra_data == tf2->extra_data) ||
+             (1 == json_equal (tf1->extra_data,
+                               tf2->extra_data)) ) &&
            (GNUNET_TIME_timestamp_cmp (tf1->valid_after,
                                        ==,
                                        tf2->valid_after)) &&
@@ -63,6 +66,12 @@ token_families_equal (const struct 
TALER_MERCHANTDB_TokenFamilyDetails *tf1,
            (GNUNET_TIME_relative_cmp (tf1->duration,
                                       ==,
                                       tf2->duration)) &&
+           (GNUNET_TIME_relative_cmp (tf1->validity_granularity,
+                                      ==,
+                                      tf2->validity_granularity)) &&
+           (GNUNET_TIME_relative_cmp (tf1->start_offset,
+                                      ==,
+                                      tf2->start_offset)) &&
            (tf1->kind == tf2->kind) );
 }
 
@@ -88,6 +97,10 @@ TMH_private_post_token_families (const struct 
TMH_RequestHandler *rh,
       GNUNET_JSON_spec_json ("description_i18n",
                              &details.description_i18n),
       NULL),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_json ("extra_data",
+                             &details.extra_data),
+      NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("valid_after",
                                   &details.valid_after),
@@ -96,8 +109,12 @@ TMH_private_post_token_families (const struct 
TMH_RequestHandler *rh,
                                 &details.valid_before),
     GNUNET_JSON_spec_relative_time ("duration",
                                     &details.duration),
-    GNUNET_JSON_spec_relative_time ("rounding",
-                                    &details.rounding),
+    GNUNET_JSON_spec_relative_time ("validity_granularity",
+                                    &details.validity_granularity),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_relative_time ("start_offset",
+                                      &details.start_offset),
+      NULL),
     GNUNET_JSON_spec_string ("kind",
                              &kind),
     GNUNET_JSON_spec_end ()
@@ -133,20 +150,23 @@ TMH_private_post_token_families (const struct 
TMH_RequestHandler *rh,
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_BAD_REQUEST,
                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                       "valid_before");
+                                       "valid_after >= valid_before");
   }
 
-  /* Ensure that valid_after is not in the past */
-  if (GNUNET_TIME_timestamp_cmp (details.valid_after,
-                                 <,
-                                 now))
+  /* Ensure that duration exceeds rounding plus start_offset */
+  if (GNUNET_TIME_relative_cmp (details.duration,
+                                <,
+                                GNUNET_TIME_relative_add (details.
+                                                          validity_granularity,
+                                                          
details.start_offset))
+      )
   {
     GNUNET_break (0);
     GNUNET_JSON_parse_free (spec);
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_BAD_REQUEST,
                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                       "valid_after");
+                                       "duration below validity_granularity 
plus start_offset");
   }
 
   if (0 ==
@@ -182,31 +202,40 @@ TMH_private_post_token_families (const struct 
TMH_RequestHandler *rh,
 
   if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS,
                                 !=,
-                                details.rounding) &&
+                                details.validity_granularity) &&
+      GNUNET_TIME_relative_cmp (GNUNET_TIME_relative_multiply (
+                                  GNUNET_TIME_UNIT_DAYS,
+                                  90),
+                                !=,
+                                details.validity_granularity) &&
       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS,
                                 !=,
-                                details.rounding) &&
+                                details.validity_granularity) &&
+      GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_WEEKS,
+                                !=,
+                                details.validity_granularity) &&
       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS,
                                 !=,
-                                details.rounding) &&
+                                details.validity_granularity) &&
       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS,
                                 !=,
-                                details.rounding) &&
+                                details.validity_granularity) &&
       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES,
                                 !=,
-                                details.rounding)
+                                details.validity_granularity)
       )
   {
     GNUNET_break (0);
     GNUNET_JSON_parse_free (spec);
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Received invalid rounding value: %s\n",
-                GNUNET_STRINGS_relative_time_to_string (details.rounding,
-                                                        true));
+                "Received invalid validity_granularity value: %s\n",
+                GNUNET_STRINGS_relative_time_to_string (details.
+                                                        validity_granularity,
+                                                        false));
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_BAD_REQUEST,
                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                       "rounding");
+                                       "validity_granularity");
   }
 
   /* finally, interact with DB until no serialization error */
@@ -215,6 +244,7 @@ TMH_private_post_token_families (const struct 
TMH_RequestHandler *rh,
     /* Test if a token family of this id is known */
     struct TALER_MERCHANTDB_TokenFamilyDetails existing;
 
+    TMH_db->preflight (TMH_db->cls);
     if (GNUNET_OK !=
         TMH_db->start (TMH_db->cls,
                        "/post tokenfamilies"))
@@ -226,69 +256,109 @@ TMH_private_post_token_families (const struct 
TMH_RequestHandler *rh,
                                          TALER_EC_GENERIC_DB_START_FAILED,
                                          NULL);
     }
-    qs = TMH_db->lookup_token_family (TMH_db->cls,
+    qs = TMH_db->insert_token_family (TMH_db->cls,
                                       mi->settings.id,
                                       details.slug,
-                                      &existing);
+                                      &details);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "insert_token_family returned %d\n",
+                (int) qs);
     switch (qs)
     {
     case GNUNET_DB_STATUS_HARD_ERROR:
-      /* Clean up and fail hard */
       GNUNET_break (0);
       TMH_db->rollback (TMH_db->cls);
       GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                         NULL);
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_INTERNAL_SERVER_ERROR,
+        TALER_EC_GENERIC_DB_STORE_FAILED,
+        "insert_token_family");
     case GNUNET_DB_STATUS_SOFT_ERROR:
-      /* restart transaction */
-      goto retry;
-    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
-      /* Good, we can proceed! */
+      GNUNET_break (0);
+      TMH_db->rollback (TMH_db->cls);
       break;
-    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
-      /* idempotency check: is existing == details? */
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      qs = TMH_db->lookup_token_family (TMH_db->cls,
+                                        mi->settings.id,
+                                        details.slug,
+                                        &existing);
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "lookup_token_family returned %d\n",
+                  (int) qs);
+      switch (qs)
       {
-        bool eq;
-
-        eq = token_families_equal (&details,
-                                   &existing);
-        TALER_MERCHANTDB_token_family_details_free (&existing);
+      case GNUNET_DB_STATUS_HARD_ERROR:
+        /* Clean up and fail hard */
+        GNUNET_break (0);
         TMH_db->rollback (TMH_db->cls);
         GNUNET_JSON_parse_free (spec);
-        return eq
-          ? TALER_MHD_reply_static (connection,
-                                    MHD_HTTP_NO_CONTENT,
-                                    NULL,
-                                    NULL,
-                                    0)
-          : TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_CONFLICT,
-                                        
TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT,
-                                        details.slug);
-      }
-    } /* end switch (qs) */
+        return TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_INTERNAL_SERVER_ERROR,
+          TALER_EC_GENERIC_DB_FETCH_FAILED,
+          "lookup_token_family");
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        TMH_db->rollback (TMH_db->cls);
+        break;
+      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+        return TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_INTERNAL_SERVER_ERROR,
+          TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+          "lookup_token_family failed after insert_token_family failed");
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+        {
+          bool eq;
 
-    qs = TMH_db->insert_token_family (TMH_db->cls,
-                                      mi->settings.id,
-                                      details.slug,
-                                      &details);
-    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-    {
-      TMH_db->rollback (TMH_db->cls);
+          eq = token_families_equal (&details,
+                                     &existing);
+          TALER_MERCHANTDB_token_family_details_free (&existing);
+          TMH_db->rollback (TMH_db->cls);
+          GNUNET_JSON_parse_free (spec);
+          return eq
+          ? TALER_MHD_reply_static (
+            connection,
+            MHD_HTTP_NO_CONTENT,
+            NULL,
+            NULL,
+            0)
+          : TALER_MHD_reply_with_error (
+            connection,
+            MHD_HTTP_CONFLICT,
+            TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT,
+            details.slug);
+        }
+      }
       break;
-    }
-    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
-    {
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
       qs = TMH_db->commit (TMH_db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+      switch (qs)
+      {
+      case GNUNET_DB_STATUS_HARD_ERROR:
+        /* Clean up and fail hard */
+        GNUNET_break (0);
+        TMH_db->rollback (TMH_db->cls);
+        GNUNET_JSON_parse_free (spec);
+        return TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_INTERNAL_SERVER_ERROR,
+          TALER_EC_GENERIC_DB_COMMIT_FAILED,
+          "insert_token_family");
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        break;
+      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
         break;
+      }
+      break;
     }
-retry:
-    GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-    TMH_db->rollback (TMH_db->cls);
-  } /* for RETRIES loop */
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      TMH_db->rollback (TMH_db->cls);
+    else
+      break;
+  } /* for(i... MAX_RETRIES) */
+
   GNUNET_JSON_parse_free (spec);
   if (qs < 0)
   {
@@ -296,9 +366,7 @@ retry:
     return TALER_MHD_reply_with_error (
       connection,
       MHD_HTTP_INTERNAL_SERVER_ERROR,
-      (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      ? TALER_EC_GENERIC_DB_SOFT_FAILURE
-      : TALER_EC_GENERIC_DB_COMMIT_FAILED,
+      TALER_EC_GENERIC_DB_SOFT_FAILURE,
       NULL);
   }
   return TALER_MHD_reply_static (connection,
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index b5a8d1fe..9b18519a 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -127,6 +127,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
   pg_delete_template.h pg_delete_template.c \
   pg_insert_template.h pg_insert_template.c \
   pg_update_template.h pg_update_template.c \
+  pg_lookup_token_family_keys.h pg_lookup_token_family_keys.c \
   pg_lookup_templates.h pg_lookup_templates.c \
   pg_lookup_template.h pg_lookup_template.c \
   pg_lookup_all_products.h pg_lookup_all_products.c \
diff --git a/src/backenddb/merchant-0008.sql b/src/backenddb/merchant-0008.sql
index 4dec99d5..2a469fab 100644
--- a/src/backenddb/merchant-0008.sql
+++ b/src/backenddb/merchant-0008.sql
@@ -44,11 +44,15 @@ COMMENT ON COLUMN merchant_issued_tokens.blind_sig
 
 ALTER TABLE merchant_spent_tokens RENAME TO merchant_used_tokens;
 
-ALTER TABLE merchant_token_families ADD COLUMN rounding BIGINT NOT NULL;
+ALTER TABLE merchant_token_families
+  ADD COLUMN rounding BIGINT NOT NULL;
+
+ALTER TABLE merchant_token_families
+  RENAME COLUMN redeemed TO used;
+
 COMMENT ON COLUMN merchant_token_families.rounding
  IS 'Token start date rounding granularity.';
 
-ALTER TABLE merchant_token_families RENAME COLUMN redeemed TO used;
 
 -- Complete transaction
 COMMIT;
diff --git a/src/backenddb/merchant-0013.sql b/src/backenddb/merchant-0013.sql
index 1e43de92..c5805505 100644
--- a/src/backenddb/merchant-0013.sql
+++ b/src/backenddb/merchant-0013.sql
@@ -28,11 +28,45 @@ SELECT _v.register_patch('merchant-0013', NULL, NULL);
 SET search_path TO merchant;
 
 -- Slug was incorrectly set to be globally unique, is only
--- unique per instance!
+-- unique per instance! Drop the constraint and add correct one.
 ALTER TABLE merchant_token_families
   DROP CONSTRAINT merchant_token_families_slug_key,
-  ADD UNIQUE (merchant_serial,slug);
-
+  ADD UNIQUE (merchant_serial,slug),
+  DROP COLUMN rounding, -- replaced by validity_granularity
+  ADD validity_granularity INT8 NOT NULL CHECK
+    (validity_granularity IN 
(60000000,3600000000,86400000000,604800000000,2592000000000,7776000000000,31536000000000))
+    DEFAULT (2592000000000), -- 30 days
+  ADD start_offset INT8 NOT NULL DEFAULT (0),
+  ADD cipher_choice TEXT NOT NULL DEFAULT ('rsa(2048)'),
+  ADD extra_data TEXT DEFAULT NULL;
+
+COMMENT ON COLUMN merchant_token_families.validity_granularity
+  IS 'To compute key lifetimes, we first round the payment deadline down to a 
multiple of this time; supported values are one minute, one hour, a day, a 
week, 30 days, 90 days or a year (indicatited using 365 days); adding the 
start_offset gets the start validity time; adding the duration to get the 
signature_valid_until value for the key';
+COMMENT ON COLUMN merchant_token_families.start_offset
+  IS 'This allows shifting the validity period of signatures to start a bit 
before the time rounded to the precision. For example, Swiss vignettes are 
valid for 14 months, from December of year X to January of year X+2. This can 
be achieve by setting a start_offset of 30 days, and a duration of 14 months 
and a precision of 1 year. The value given is in microseconds (but will be 
rounded to seconds).';
+COMMENT ON COLUMN merchant_token_families.cipher_choice
+  IS 'Specifies the type of cipher that should be used for this token family. 
Currently supported values are "cs" and "rsa($LEN)" where $LEN is the key 
length in bits.';
+COMMENT ON COLUMN merchant_token_families.extra_data
+  IS 'JSON field with family-specific meta data, such as the trusted_domains 
for subscriptions or expected_domains for discount tokens';
+
+
+-- Keep proper periods for token keys.
+ALTER TABLE merchant_token_family_keys
+  DROP valid_before,
+  DROP valid_after,
+  ADD signature_validity_start INT8 NOT NULL DEFAULT (0),
+  ADD signature_validity_end INT8 NOT NULL DEFAULT (0),
+  ADD private_key_deleted_at INT8 NOT NULL DEFAULT (0),
+  ADD private_key_created_at INT8 NOT NULL DEFAULT (0);
+
+COMMENT ON COLUMN merchant_token_family_keys.signature_validity_start
+  IS 'Specifies the earliest time at which tokens signed with this key can be 
considered valid. Allows tokens to be issued way in advance of their validity.';
+COMMENT ON COLUMN merchant_token_family_keys.signature_validity_end
+  IS 'Specifies when the tokens signed by this key expire.';
+COMMENT ON COLUMN merchant_token_family_keys.private_key_deleted_at
+  IS 'Specifies how long tokens signed by this key can be created, that is the 
point at which the private key may be deleted. Computed by determining when the 
*next* validity period starts, or when the overall token family validity period 
ends.';
+COMMENT ON COLUMN merchant_token_family_keys.private_key_created_at
+  IS 'Specifies when the private key was created. Not terribly useful, mostly 
for debugging.';
 
 -- Function to replace placeholders in a string with a given value
 CREATE OR REPLACE FUNCTION replace_placeholder(
diff --git a/src/backenddb/pg_insert_token_family.c 
b/src/backenddb/pg_insert_token_family.c
index 81a1890b..c416b60e 100644
--- a/src/backenddb/pg_insert_token_family.c
+++ b/src/backenddb/pg_insert_token_family.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2023 Taler Systems SA
+   Copyright (C) 2023, 2024 Taler Systems SA
 
    TALER is free software; you can redistribute it and/or modify it under the
    terms of the GNU General Public License as published by the Free Software
@@ -25,12 +25,13 @@
 #include "pg_insert_token_family.h"
 #include "pg_helper.h"
 
+
 enum GNUNET_DB_QueryStatus
-TMH_PG_insert_token_family (void *cls,
-                            const char *instance_id,
-                            const char *token_family_slug,
-                            const struct TALER_MERCHANTDB_TokenFamilyDetails *
-                            details)
+TMH_PG_insert_token_family (
+  void *cls,
+  const char *instance_id,
+  const char *token_family_slug,
+  const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
 {
   struct PostgresClosure *pg = cls;
   const char *kind;
@@ -47,7 +48,6 @@ TMH_PG_insert_token_family (void *cls,
     GNUNET_break (0);
     return GNUNET_DB_STATUS_HARD_ERROR;
   }
-
   check_connection (pg);
   PREPARE (pg,
            "insert_token_family",
@@ -57,14 +57,17 @@ TMH_PG_insert_token_family (void *cls,
            ",name"
            ",description"
            ",description_i18n"
+           ",extra_data"
            ",valid_after"
            ",valid_before"
            ",duration"
-           ",rounding"
+           ",validity_granularity"
+           ",start_offset"
            ",kind)"
-           " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9, $10"
+           " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 
$12"
            " FROM merchant_instances"
-           " WHERE merchant_id=$1");
+           " WHERE merchant_id=$1"
+           " ON CONFLICT DO NOTHING;");
   {
     struct GNUNET_PQ_QueryParam params[] = {
       GNUNET_PQ_query_param_string (instance_id),
@@ -72,10 +75,14 @@ TMH_PG_insert_token_family (void *cls,
       GNUNET_PQ_query_param_string (details->name),
       GNUNET_PQ_query_param_string (details->description),
       TALER_PQ_query_param_json (details->description_i18n),
+      NULL == details->extra_data
+      ? GNUNET_PQ_query_param_null ()
+      : TALER_PQ_query_param_json (details->extra_data),
       GNUNET_PQ_query_param_timestamp (&details->valid_after),
       GNUNET_PQ_query_param_timestamp (&details->valid_before),
       GNUNET_PQ_query_param_relative_time (&details->duration),
-      GNUNET_PQ_query_param_relative_time (&details->rounding),
+      GNUNET_PQ_query_param_relative_time (&details->validity_granularity),
+      GNUNET_PQ_query_param_relative_time (&details->start_offset),
       GNUNET_PQ_query_param_string (kind),
       GNUNET_PQ_query_param_end
     };
diff --git a/src/backenddb/pg_insert_token_family_key.c 
b/src/backenddb/pg_insert_token_family_key.c
index 1f441e49..15b57834 100644
--- a/src/backenddb/pg_insert_token_family_key.c
+++ b/src/backenddb/pg_insert_token_family_key.c
@@ -35,10 +35,13 @@ TMH_PG_insert_token_family_key (
   const char *token_family_slug,
   const struct TALER_TokenIssuePublicKey *pub,
   const struct TALER_TokenIssuePrivateKey *priv,
-  const struct GNUNET_TIME_Timestamp valid_after,
-  const struct GNUNET_TIME_Timestamp valid_before)
+  struct GNUNET_TIME_Timestamp key_expires,
+  struct GNUNET_TIME_Timestamp valid_after,
+  struct GNUNET_TIME_Timestamp valid_before)
 {
   struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Timestamp now
+    = GNUNET_TIME_timestamp_get ();
   const char *cipher = NULL;
   struct GNUNET_HashCode pub_hash;
 
@@ -46,14 +49,16 @@ TMH_PG_insert_token_family_key (
   {
   case GNUNET_CRYPTO_BSA_RSA:
     cipher = "rsa";
-    GNUNET_CRYPTO_rsa_public_key_hash (pub->public_key->details.rsa_public_key,
-                                       &pub_hash);
+    GNUNET_CRYPTO_rsa_public_key_hash (
+      pub->public_key->details.rsa_public_key,
+      &pub_hash);
     break;
   case GNUNET_CRYPTO_BSA_CS:
     cipher = "cs";
-    GNUNET_CRYPTO_hash (&pub->public_key->details.cs_public_key,
-                        sizeof (pub->public_key->details.cs_public_key),
-                        &pub_hash);
+    GNUNET_CRYPTO_hash (
+      &pub->public_key->details.cs_public_key,
+      sizeof (pub->public_key->details.cs_public_key),
+      &pub_hash);
     break;
   case GNUNET_CRYPTO_BSA_INVALID:
     GNUNET_break (0);
@@ -68,7 +73,6 @@ TMH_PG_insert_token_family_key (
                    valid_after.abs_time));
   GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
                    valid_before.abs_time));
-
   PREPARE (pg,
            "token_family_key_insert",
            "INSERT INTO merchant_token_family_keys "
@@ -76,31 +80,43 @@ TMH_PG_insert_token_family_key (
            ",pub"
            ",h_pub"
            ",priv"
-           ",valid_after"
-           ",valid_before"
+           ",private_key_created_at"
+           ",private_key_deleted_at"
+           ",signature_validity_start"
+           ",signature_validity_end"
            ",cipher)"
-           " SELECT token_family_serial, $2, $3, $4, $5, $6, $7"
+           " SELECT token_family_serial, $2, $3, $4, $5, $6, $7, $8, $9"
            " FROM merchant_token_families"
            " WHERE (slug = $1)"
            "   AND merchant_serial="
            "   (SELECT merchant_serial"
            "      FROM merchant_instances"
-           "     WHERE merchant_id=$8)");
+           "     WHERE merchant_id=$10)");
   {
     struct GNUNET_PQ_QueryParam params[] = {
       GNUNET_PQ_query_param_string (token_family_slug),
       GNUNET_PQ_query_param_blind_sign_pub (pub->public_key),
       GNUNET_PQ_query_param_auto_from_type (&pub->public_key->pub_key_hash),
       GNUNET_PQ_query_param_blind_sign_priv (priv->private_key),
+      GNUNET_PQ_query_param_timestamp (&now),
+      GNUNET_PQ_query_param_timestamp (&key_expires),
       GNUNET_PQ_query_param_timestamp (&valid_after),
       GNUNET_PQ_query_param_timestamp (&valid_before),
       GNUNET_PQ_query_param_string (cipher),
       GNUNET_PQ_query_param_string (merchant_id),
       GNUNET_PQ_query_param_end
     };
+    enum GNUNET_DB_QueryStatus qs;
 
-    return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                               "token_family_key_insert",
-                                               params);
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "token_family_key_insert",
+                                             params);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Insert into MTFK %s with valid [%llu,%llu] got %d\n",
+                token_family_slug,
+                (unsigned long long) valid_after.abs_time.abs_value_us,
+                (unsigned long long) valid_before.abs_time.abs_value_us,
+                (int) qs);
+    return qs;
   }
 }
diff --git a/src/backenddb/pg_insert_token_family_key.h 
b/src/backenddb/pg_insert_token_family_key.h
index c8c85351..01910beb 100644
--- a/src/backenddb/pg_insert_token_family_key.h
+++ b/src/backenddb/pg_insert_token_family_key.h
@@ -34,8 +34,10 @@
  * @param token_family_slug slug of the token family to insert the key for
  * @param pub public key to insert
  * @param priv private key to insert
- * @param valid_after start of validity period for this key
- * @param valid_before end of validity period for this key
+ * @param key_expires when does the private key expire (because
+ *     the validity period of the next token family key starts)
+ * @param valid_after start of validity period for signatures with this key
+ * @param valid_before end of validity period for signatures with this key
  * @return database result code
  */
 enum GNUNET_DB_QueryStatus
@@ -45,7 +47,8 @@ TMH_PG_insert_token_family_key (
   const char *token_family_slug,
   const struct TALER_TokenIssuePublicKey *pub,
   const struct TALER_TokenIssuePrivateKey *priv,
-  const struct GNUNET_TIME_Timestamp valid_after,
-  const struct GNUNET_TIME_Timestamp valid_before);
+  struct GNUNET_TIME_Timestamp key_expires,
+  struct GNUNET_TIME_Timestamp valid_after,
+  struct GNUNET_TIME_Timestamp valid_before);
 
 #endif
diff --git a/src/backenddb/pg_lookup_token_family.c 
b/src/backenddb/pg_lookup_token_family.c
index 943e0ee7..a489160d 100644
--- a/src/backenddb/pg_lookup_token_family.c
+++ b/src/backenddb/pg_lookup_token_family.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2023 Taler Systems SA
+   Copyright (C) 2023, 2024 Taler Systems SA
 
    TALER is free software; you can redistribute it and/or modify it under the
    terms of the GNU General Public License as published by the Free Software
@@ -25,11 +25,13 @@
 #include "pg_lookup_token_family.h"
 #include "pg_helper.h"
 
+
 enum GNUNET_DB_QueryStatus
-TMH_PG_lookup_token_family (void *cls,
-                            const char *instance_id,
-                            const char *token_family_slug,
-                            struct TALER_MERCHANTDB_TokenFamilyDetails 
*details)
+TMH_PG_lookup_token_family (
+  void *cls,
+  const char *instance_id,
+  const char *token_family_slug,
+  struct TALER_MERCHANTDB_TokenFamilyDetails *details)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
@@ -62,12 +64,20 @@ TMH_PG_lookup_token_family (void *cls,
                                     &details->description),
       TALER_PQ_result_spec_json ("description_i18n",
                                  &details->description_i18n),
+      GNUNET_PQ_result_spec_allow_null (
+        TALER_PQ_result_spec_json ("extra_data",
+                                   &details->extra_data),
+        NULL),
       GNUNET_PQ_result_spec_timestamp ("valid_after",
                                        &details->valid_after),
       GNUNET_PQ_result_spec_timestamp ("valid_before",
                                        &details->valid_before),
       GNUNET_PQ_result_spec_relative_time ("duration",
                                            &details->duration),
+      GNUNET_PQ_result_spec_relative_time ("validity_granularity",
+                                           &details->validity_granularity),
+      GNUNET_PQ_result_spec_relative_time ("start_offset",
+                                           &details->start_offset),
       GNUNET_PQ_result_spec_string ("kind",
                                     &kind),
       GNUNET_PQ_result_spec_uint64 ("issued",
@@ -86,9 +96,12 @@ TMH_PG_lookup_token_family (void *cls,
              ",name"
              ",description"
              ",description_i18n"
+             ",extra_data"
              ",valid_after"
              ",valid_before"
              ",duration"
+             ",validity_granularity"
+             ",start_offset"
              ",kind"
              ",issued"
              ",used"
@@ -97,6 +110,9 @@ TMH_PG_lookup_token_family (void *cls,
              "   USING (merchant_serial)"
              " WHERE merchant_instances.merchant_id=$1"
              "   AND merchant_token_families.slug=$2");
+    memset (details,
+            0,
+            sizeof (*details));
     qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                    "lookup_token_family",
                                                    params,
diff --git a/src/backenddb/pg_lookup_token_family_key.c 
b/src/backenddb/pg_lookup_token_family_key.c
index 61ae93fb..153c92be 100644
--- a/src/backenddb/pg_lookup_token_family_key.c
+++ b/src/backenddb/pg_lookup_token_family_key.c
@@ -34,16 +34,16 @@ TMH_PG_lookup_token_family_key (
   void *cls,
   const char *instance_id,
   const char *token_family_slug,
-  struct GNUNET_TIME_Timestamp min_valid_after,
-  struct GNUNET_TIME_Timestamp max_valid_after,
+  struct GNUNET_TIME_Timestamp valid_at,
+  struct GNUNET_TIME_Timestamp sign_until,
   struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (instance_id),
     GNUNET_PQ_query_param_string (token_family_slug),
-    GNUNET_PQ_query_param_timestamp (&min_valid_after),
-    GNUNET_PQ_query_param_timestamp (&max_valid_after),
+    GNUNET_PQ_query_param_timestamp (&valid_at),
+    GNUNET_PQ_query_param_timestamp (&sign_until),
     GNUNET_PQ_query_param_end
   };
 
@@ -54,29 +54,33 @@ TMH_PG_lookup_token_family_key (
            " h_pub"
            ",pub"
            ",priv"
-           ",cipher"
-           ",merchant_token_family_keys.valid_after AS key_valid_after"
-           ",merchant_token_family_keys.valid_before AS key_valid_before"
+           ",cipher_choice"
+           ",mtfk.signature_validity_start"
+           ",mtfk.signature_validity_end"
+           ",mtfk.private_key_deleted_at"
            ",slug"
            ",name"
            ",description"
            ",description_i18n"
-           ",merchant_token_families.valid_after"
-           ",merchant_token_families.valid_before"
+           ",mtf.valid_after"
+           ",mtf.valid_before"
            ",duration"
+           ",validity_granularity"
+           ",start_offset"
            ",kind"
            ",issued"
            ",used"
-           " FROM merchant_token_families"
-           " LEFT JOIN merchant_token_family_keys"
-           "   ON merchant_token_families.token_family_serial = 
merchant_token_family_keys.token_family_serial"
-           "   AND merchant_token_family_keys.valid_after >= $3"
-           "   AND merchant_token_family_keys.valid_after <= $4"  /* TODO: 
Should this be < instead of <=? */
-           " JOIN merchant_instances"
+           " FROM merchant_token_families mtf"
+           " LEFT JOIN merchant_token_family_keys mtfk"
+           "  USING (token_family_serial)"
+           " JOIN merchant_instances mi"
            "   USING (merchant_serial)"
-           " WHERE merchant_instances.merchant_id=$1"
+           " WHERE mi.merchant_id=$1"
            "   AND slug=$2"
-           " ORDER BY merchant_token_family_keys.valid_after ASC"
+           "   AND COALESCE ($3 >= mtfk.signature_validity_start, TRUE)"
+           "   AND COALESCE ($3 <= mtfk.signature_validity_end, TRUE)"
+           "   AND COALESCE ($4 <= mtfk.private_key_deleted_at, TRUE)"
+           " ORDER BY mtfk.signature_validity_start ASC"
            " LIMIT 1");
 
   if (NULL == details)
@@ -85,7 +89,6 @@ TMH_PG_lookup_token_family_key (
       GNUNET_PQ_result_spec_end
     };
 
-    check_connection (pg);
     return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                      "lookup_token_family_key",
                                                      params,
@@ -105,17 +108,23 @@ TMH_PG_lookup_token_family_key (
                                                &details->priv.private_key),
         NULL),
       GNUNET_PQ_result_spec_allow_null (
-        GNUNET_PQ_result_spec_timestamp ("key_valid_after",
-                                         &details->valid_after),
+        GNUNET_PQ_result_spec_timestamp ("signature_validity_start",
+                                         &details->signature_validity_start),
         NULL),
       GNUNET_PQ_result_spec_allow_null (
-        GNUNET_PQ_result_spec_timestamp ("key_valid_before",
-                                         &details->valid_before),
+        GNUNET_PQ_result_spec_timestamp ("signature_validity_end",
+                                         &details->signature_validity_end),
+        NULL),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_timestamp ("private_key_deleted_at",
+                                         &details->private_key_deleted_at),
         NULL),
       GNUNET_PQ_result_spec_string ("slug",
                                     &details->token_family.slug),
       GNUNET_PQ_result_spec_string ("name",
                                     &details->token_family.name),
+      GNUNET_PQ_result_spec_string ("cipher_choice",
+                                    &details->token_family.cipher_spec),
       GNUNET_PQ_result_spec_string ("description",
                                     &details->token_family.description),
       TALER_PQ_result_spec_json ("description_i18n",
@@ -126,6 +135,11 @@ TMH_PG_lookup_token_family_key (
                                        &details->token_family.valid_before),
       GNUNET_PQ_result_spec_relative_time ("duration",
                                            &details->token_family.duration),
+      GNUNET_PQ_result_spec_relative_time ("validity_granularity",
+                                           &details->token_family.
+                                           validity_granularity),
+      GNUNET_PQ_result_spec_relative_time ("start_offset",
+                                           
&details->token_family.start_offset),
       GNUNET_PQ_result_spec_string ("kind",
                                     &kind),
       GNUNET_PQ_result_spec_uint64 ("issued",
diff --git a/src/backenddb/pg_lookup_token_family_key.h 
b/src/backenddb/pg_lookup_token_family_key.h
index fd88887c..c6828ffa 100644
--- a/src/backenddb/pg_lookup_token_family_key.h
+++ b/src/backenddb/pg_lookup_token_family_key.h
@@ -33,19 +33,20 @@
  * @param cls closure
  * @param instance_id instance to lookup token family key for
  * @param token_family_slug slug of token family to lookup
- * @param min_valid_after lower bound of the start of the key validation period
- * @param max_valid_after strict upper bound of the start of the key 
validation period
+ * @param valid_at find a key with a validity period that includes this time
+ * @param sign_until find a private key that can sign until this time
  * @param[out] details set to the token family key details on success, can be 
NULL
  *             (in that case we only want to check if the token family key 
exists)
  * @return database result code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_lookup_token_family_key (void *cls,
-                                const char *instance_id,
-                                const char *token_family_slug,
-                                struct GNUNET_TIME_Timestamp min_valid_after,
-                                struct GNUNET_TIME_Timestamp max_valid_after,
-                                struct TALER_MERCHANTDB_TokenFamilyKeyDetails 
*details);
+TMH_PG_lookup_token_family_key (
+  void *cls,
+  const char *instance_id,
+  const char *token_family_slug,
+  struct GNUNET_TIME_Timestamp valid_at,
+  struct GNUNET_TIME_Timestamp sign_until,
+  struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
 
 
 #endif
diff --git a/src/backenddb/pg_lookup_token_family_keys.c 
b/src/backenddb/pg_lookup_token_family_keys.c
new file mode 100644
index 00000000..6136bf2c
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family_keys.c
@@ -0,0 +1,225 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2024 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_token_family_keys.c
+ * @brief Implementation of the lookup_token_family_keys function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_token_family_keys.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for lookup_token_keys_cb().
+ */
+struct Context
+{
+  /**
+   * Postgres handle.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Function to call on each result.
+   */
+  TALER_MERCHANTDB_TokenKeyCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Did database result extraction fail?
+   */
+  bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about products.
+ *
+ * @param[in,out] cls of type `struct LookupProductsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_token_keys_cb (void *cls,
+                      PGresult *result,
+                      unsigned int num_results)
+{
+  struct Context *ctx = cls;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
+    char *kind;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_blind_sign_pub ("pub",
+                                              &details.pub.public_key),
+        NULL),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_blind_sign_priv ("priv",
+                                               &details.priv.private_key),
+        NULL),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_timestamp ("signature_validity_start",
+                                         &details.signature_validity_start),
+        NULL),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_timestamp ("signature_validity_end",
+                                         &details.signature_validity_end),
+        NULL),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_timestamp ("private_key_deleted_at",
+                                         &details.private_key_deleted_at),
+        NULL),
+      GNUNET_PQ_result_spec_string ("slug",
+                                    &details.token_family.slug),
+      GNUNET_PQ_result_spec_string ("name",
+                                    &details.token_family.name),
+      GNUNET_PQ_result_spec_string ("description",
+                                    &details.token_family.description),
+      GNUNET_PQ_result_spec_string ("cipher_choice",
+                                    &details.token_family.cipher_spec),
+      TALER_PQ_result_spec_json ("description_i18n",
+                                 &details.token_family.description_i18n),
+      GNUNET_PQ_result_spec_timestamp ("valid_after",
+                                       &details.token_family.valid_after),
+      GNUNET_PQ_result_spec_timestamp ("valid_before",
+                                       &details.token_family.valid_before),
+      GNUNET_PQ_result_spec_relative_time ("duration",
+                                           &details.token_family.duration),
+      GNUNET_PQ_result_spec_relative_time ("validity_granularity",
+                                           &details.token_family.
+                                           validity_granularity),
+      GNUNET_PQ_result_spec_relative_time ("start_offset",
+                                           &details.token_family.start_offset),
+      GNUNET_PQ_result_spec_string ("kind",
+                                    &kind),
+      GNUNET_PQ_result_spec_uint64 ("issued",
+                                    &details.token_family.issued),
+      GNUNET_PQ_result_spec_uint64 ("used",
+                                    &details.token_family.used),
+      GNUNET_PQ_result_spec_end
+    };
+
+    memset (&details,
+            0,
+            sizeof (details));
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ctx->extract_failed = true;
+      return;
+    }
+    if (0 == strcmp (kind,
+                     "discount"))
+      details.token_family.kind = TALER_MERCHANTDB_TFK_Discount;
+    else if (0 == strcmp (kind,
+                          "subscription"))
+      details.token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
+    else
+    {
+      GNUNET_break (0);
+      GNUNET_free (kind);
+      GNUNET_PQ_cleanup_result (rs);
+      return;
+    }
+    ctx->cb (ctx->cb_cls,
+             &details);
+    if (NULL != details.pub.public_key) /* guard against GNUnet 0.23 bug! */
+      GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family_keys (
+  void *cls,
+  const char *instance_id,
+  const char *token_family_slug,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  TALER_MERCHANTDB_TokenKeyCallback cb,
+  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (token_family_slug),
+    GNUNET_PQ_query_param_timestamp (&start_time),
+    GNUNET_PQ_query_param_timestamp (&end_time),
+    GNUNET_PQ_query_param_end
+  };
+  struct Context ctx = {
+    .pg = pg,
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  check_connection (pg);
+  PREPARE (pg,
+           "lookup_token_family_keys",
+           "SELECT"
+           " h_pub"
+           ",mtfk.pub"
+           ",mtfk.priv"
+           ",cipher_choice"
+           ",mtfk.signature_validity_start"
+           ",mtfk.signature_validity_end"
+           ",mtfk.private_key_deleted_at"
+           ",slug"
+           ",name"
+           ",description"
+           ",description_i18n"
+           ",mtf.valid_after"
+           ",mtf.valid_before"
+           ",duration"
+           ",validity_granularity"
+           ",start_offset"
+           ",kind"
+           ",issued"
+           ",used"
+           " FROM merchant_token_families mtf"
+           " LEFT JOIN merchant_token_family_keys mtfk"
+           "   USING (token_family_serial)"
+           " JOIN merchant_instances mi"
+           "   USING (merchant_serial)"
+           " WHERE mi.merchant_id=$1"
+           "   AND slug=$2"
+           "   AND COALESCE ($3 <= mtfk.signature_validity_end, TRUE)"
+           "   AND COALESCE ($4 >= mtfk.signature_validity_start, TRUE);");
+  qs = GNUNET_PQ_eval_prepared_multi_select (
+    pg->conn,
+    "lookup_token_family_keys",
+    params,
+    &lookup_token_keys_cb,
+    &ctx);
+  if (ctx.extract_failed)
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  return qs;
+}
diff --git a/src/backenddb/pg_insert_token_family_key.h 
b/src/backenddb/pg_lookup_token_family_keys.h
similarity index 53%
copy from src/backenddb/pg_insert_token_family_key.h
copy to src/backenddb/pg_lookup_token_family_keys.h
index c8c85351..bd091429 100644
--- a/src/backenddb/pg_insert_token_family_key.h
+++ b/src/backenddb/pg_lookup_token_family_keys.h
@@ -14,12 +14,12 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_insert_token_family_key.h
- * @brief implementation of the insert_token_family_key function for Postgres
- * @author Christian Blättler
+ * @file backenddb/pg_lookup_token_family_keys.h
+ * @brief implementation of the lookup_token_family_keys function for Postgres
+ * @author Christian Grothoff
  */
-#ifndef PG_INSERT_TOKEN_FAMILY_KEY_H
-#define PG_INSERT_TOKEN_FAMILY_KEY_H
+#ifndef PG_LOOKUP_TOKEN_FAMILY_KEYS_H
+#define PG_LOOKUP_TOKEN_FAMILY_KEYS_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
@@ -27,25 +27,26 @@
 
 
 /**
- * Insert new key pair for a token family.
+ * Lookup token family keys that may be used for a payment.
  *
  * @param cls closure
- * @param merchant_id instance name
- * @param token_family_slug slug of the token family to insert the key for
- * @param pub public key to insert
- * @param priv private key to insert
- * @param valid_after start of validity period for this key
- * @param valid_before end of validity period for this key
+ * @param instance_id instance to lookup token family key for
+ * @param token_family_slug slug of token family to lookup
+ * @param start_time signature validity start the keys must fall into
+ * @param end_time signature validity end the keys must fall into
+ * @param cb function to call with each matching key
+ * @param cb_cls closure for @a cb
  * @return database result code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_insert_token_family_key (
+TMH_PG_lookup_token_family_keys (
   void *cls,
-  const char *merchant_id,
+  const char *instance_id,
   const char *token_family_slug,
-  const struct TALER_TokenIssuePublicKey *pub,
-  const struct TALER_TokenIssuePrivateKey *priv,
-  const struct GNUNET_TIME_Timestamp valid_after,
-  const struct GNUNET_TIME_Timestamp valid_before);
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  TALER_MERCHANTDB_TokenKeyCallback cb,
+  void *cb_cls);
+
 
 #endif
diff --git a/src/backenddb/pg_update_token_family.c 
b/src/backenddb/pg_update_token_family.c
index 7864c60b..ea7a122f 100644
--- a/src/backenddb/pg_update_token_family.c
+++ b/src/backenddb/pg_update_token_family.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2023 Taler Systems SA
+   Copyright (C) 2023, 2024 Taler Systems SA
 
    TALER is free software; you can redistribute it and/or modify it under the
    terms of the GNU General Public License as published by the Free Software
@@ -25,11 +25,13 @@
 #include "pg_update_token_family.h"
 #include "pg_helper.h"
 
+
 enum GNUNET_DB_QueryStatus
-TMH_PG_update_token_family (void *cls,
-                            const char *instance_id,
-                            const char *token_family_slug,
-                            const struct TALER_MERCHANTDB_TokenFamilyDetails 
*details)
+TMH_PG_update_token_family (
+  void *cls,
+  const char *instance_id,
+  const char *token_family_slug,
+  const struct TALER_MERCHANTDB_TokenFamilyDetails *details)
 {
   struct PostgresClosure *pg = cls;
 
@@ -39,9 +41,9 @@ TMH_PG_update_token_family (void *cls,
     GNUNET_PQ_query_param_string (details->name),
     GNUNET_PQ_query_param_string (details->description),
     TALER_PQ_query_param_json (details->description_i18n),
+    TALER_PQ_query_param_json (details->extra_data),
     GNUNET_PQ_query_param_timestamp (&details->valid_after),
     GNUNET_PQ_query_param_timestamp (&details->valid_before),
-    GNUNET_PQ_query_param_relative_time (&details->duration),
     GNUNET_PQ_query_param_end
   };
 
@@ -52,9 +54,9 @@ TMH_PG_update_token_family (void *cls,
            " name=$3"
            ",description=$4"
            ",description_i18n=$5"
-           ",valid_after=$6"
-           ",valid_before=$7"
-           ",duration=$8"
+           ",extra_data=$6"
+           ",valid_after=$7"
+           ",valid_before=$8"
            " WHERE merchant_serial="
            "   (SELECT merchant_serial"
            "      FROM merchant_instances"
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 59e3f2fb..21d265d4 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -77,6 +77,7 @@
 #include "pg_delete_product.h"
 #include "pg_insert_product.h"
 #include "pg_update_product.h"
+#include "pg_lookup_token_family_keys.h"
 #include "pg_lock_product.h"
 #include "pg_expire_locks.h"
 #include "pg_delete_order.h"
@@ -551,6 +552,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
     = &TMH_PG_insert_deposit_to_transfer;
   plugin->insert_transfer
     = &TMH_PG_insert_transfer;
+  plugin->lookup_token_family_keys
+    = &TMH_PG_lookup_token_family_keys;
   plugin->insert_transfer_details
     = &TMH_PG_insert_transfer_details;
   plugin->store_wire_fee_by_exchange
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index a97c1eb7..b4f351e7 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -1980,6 +1980,11 @@ struct TALER_MERCHANT_TokenFamilyGetResponse
        */
       const json_t *description_i18n;
 
+      /**
+       * Additional data about the token family (such as expected_domains).
+       */
+      const json_t *extra_data;
+
       /**
        * Start time of the token family's validity period.
        */
@@ -1995,6 +2000,17 @@ struct TALER_MERCHANT_TokenFamilyGetResponse
        */
       struct GNUNET_TIME_Relative duration;
 
+      /**
+       * Granularity of the validity periods of the token.
+       */
+      struct GNUNET_TIME_Relative validity_granularity;
+
+      /**
+       * Offset subtracted from the rounded start time to determine
+       * the actual start time.
+       */
+      struct GNUNET_TIME_Relative start_offset;
+
       /**
        * Kind of token family, "subscription" or "discount".
        */
@@ -2064,10 +2080,13 @@ typedef void
  * @param name human-readable name for the token family
  * @param description description of the token family
  * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param extra_data additional meta-data about the token family
  * @param valid_after when the token family becomes valid
  * @param valid_before when the token family expires
  * @param duration how long tokens issued by this token family are valid for
- * @param rounding rounding duration of token family
+ * @param validity_granularity rounding duration of token family
+ * @param start_offset time to subtract after rounding down to get
+ *        the actual start time for each round
  * @param kind kind of token family, "subscription" or "discount"
  * @param cb function to call with the backend's result
  * @param cb_cls closure for @a cb
@@ -2081,14 +2100,17 @@ TALER_MERCHANT_token_families_post (
   const char *name,
   const char *description,
   const json_t *description_i18n,
+  const json_t *extra_data,
   struct GNUNET_TIME_Timestamp valid_after,
   struct GNUNET_TIME_Timestamp valid_before,
   struct GNUNET_TIME_Relative duration,
-  struct GNUNET_TIME_Relative rounding,
+  struct GNUNET_TIME_Relative validity_granularity,
+  struct GNUNET_TIME_Relative start_offset,
   const char *kind,
   TALER_MERCHANT_TokenFamiliesPostCallback cb,
   void *cb_cls);
 
+
 /**
  * Cancel POST /tokenfamilies operation.
  *
@@ -2098,6 +2120,10 @@ void
 TALER_MERCHANT_token_families_post_cancel (
   struct TALER_MERCHANT_TokenFamiliesPostHandle *handle);
 
+
+/* FIXME: token_families_patch API is missing... */
+
+
 /* ********************* /orders ************************** */
 
 
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 60590047..5951758c 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -1209,6 +1209,20 @@ struct TALER_MERCHANTDB_TokenFamilyDetails
    */
   json_t *description_i18n;
 
+  /**
+   * Meta-data associated with the token family.
+   * Includes information like "trusted_domains" or
+   * "expected_domains", if set.
+   */
+  json_t *extra_data;
+
+  /**
+   * Cipher that should be used for this token family.  Note: We do not expose
+   * this over the API and do not let clients set it. NULL for default (when
+   * calling database).
+   */
+  char *cipher_spec;
+
   /**
    * Start time of the token family duration.
    */
@@ -1220,14 +1234,21 @@ struct TALER_MERCHANTDB_TokenFamilyDetails
   struct GNUNET_TIME_Timestamp valid_before;
 
   /**
-   * Validity duration of the token family.
+   * Validity duration of the token family. Must be larger or
+   * equal to @a rounding plus @a start_offset_s.
    */
   struct GNUNET_TIME_Relative duration;
 
   /**
    * Rounding duration of the token family.
    */
-  struct GNUNET_TIME_Relative rounding;
+  struct GNUNET_TIME_Relative validity_granularity;
+
+  /**
+   * Offset (in seconds) to subtract from the rounded
+   * validity start period.
+   */
+  struct GNUNET_TIME_Relative start_offset;
 
   /**
    * Token family kind.
@@ -1253,13 +1274,20 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails
 {
   /**
    * Tokens signed with this key are valid from this date on.
+   * This is the time the key was created.
    */
-  struct GNUNET_TIME_Timestamp valid_after;
+  struct GNUNET_TIME_Timestamp signature_validity_start; // valid_after;
 
   /**
    * Tokens signed with this key are valid until this date.
    */
-  struct GNUNET_TIME_Timestamp valid_before;
+  struct GNUNET_TIME_Timestamp signature_validity_end; // valid_before;
+
+  /**
+   * Private key expires for use at this time. Signatures can
+   * only be created until this point.
+   */
+  struct GNUNET_TIME_Timestamp private_key_deleted_at;
 
   /**
    * Token family public key.
@@ -1277,9 +1305,22 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails
   struct TALER_MERCHANTDB_TokenFamilyDetails token_family;
 };
 
+
+/**
+ * Function called with applicable token keys.
+ *
+ * @param cls closure
+ * @param details details about an applicable key
+ */
+typedef void
+(*TALER_MERCHANTDB_TokenKeyCallback) (
+  void *cls,
+  const struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
+
+
 /**
  * Details about a spent token.
-*/
+ */
 struct TALER_MERCHANTDB_SpentTokenDetails
 {
   /**
@@ -3739,8 +3780,8 @@ struct TALER_MERCHANTDB_Plugin
   * @param cls closure
   * @param instance_id instance to lookup token family key for
   * @param token_family_slug slug of token family to lookup
-  * @param min_valid_after lower bound of the start of the key validation 
period
-  * @param max_valid_after upper bound of the start of the key validation 
period
+  * @param valid_at find a key with a validity period that includes this time
+  * @param sign_until find a private key that can sign until this time
   * @param[out] details set to the token family key details on success, can be 
NULL
   *             (in that case we only want to check if the token family key 
exists)
   * @return database result code
@@ -3750,11 +3791,34 @@ struct TALER_MERCHANTDB_Plugin
     void *cls,
     const char *instance_id,
     const char *token_family_slug,
-    struct GNUNET_TIME_Timestamp min_valid_after,
-    struct GNUNET_TIME_Timestamp max_valid_after,
+    struct GNUNET_TIME_Timestamp valid_at,
+    struct GNUNET_TIME_Timestamp sign_until,
     struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
 
 
+  /**
+   * Lookup token family keys that may be used for a payment.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup token family key for
+   * @param token_family_slug slug of token family to lookup
+   * @param start_time signature validity start the keys must fall into
+   * @param end_time signature validity end the keys must fall into
+   * @param cb function to call with each matching key
+   * @param cb_cls closure for @a cb
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_token_family_keys)(
+    void *cls,
+    const char *instance_id,
+    const char *token_family_slug,
+    struct GNUNET_TIME_Timestamp start_time,
+    struct GNUNET_TIME_Timestamp end_time,
+    TALER_MERCHANTDB_TokenKeyCallback cb,
+    void *cb_cls);
+
+
   /**
    * Insert details a key pair for a token family.
    *
@@ -3763,8 +3827,10 @@ struct TALER_MERCHANTDB_Plugin
    * @param token_family_slug slug of token family to insert the key pair for
    * @param pub token family public key
    * @param priv token family private key
-   * @param valid_after start of the key validation period
-   * @param valid_before end of the key validation period
+   * @param key_expires when does the private key expire (because
+   *     the validity period of the next token family key starts)
+   * @param valid_after start of validity period for signatures with this key
+   * @param valid_before end of validity period for signatures with this key
    * @return database result code
    */
   enum GNUNET_DB_QueryStatus
@@ -3774,6 +3840,7 @@ struct TALER_MERCHANTDB_Plugin
     const char *token_family_slug,
     const struct TALER_TokenIssuePublicKey *pub,
     const struct TALER_TokenIssuePrivateKey *priv,
+    struct GNUNET_TIME_Timestamp key_expires,
     struct GNUNET_TIME_Timestamp valid_after,
     struct GNUNET_TIME_Timestamp valid_before);
 
diff --git a/src/lib/merchant_api_get_tokenfamily.c 
b/src/lib/merchant_api_get_tokenfamily.c
index 232d30d6..6c42a4e4 100644
--- a/src/lib/merchant_api_get_tokenfamily.c
+++ b/src/lib/merchant_api_get_tokenfamily.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2023 Taler Systems SA
+  Copyright (C) 2023-2024 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License as published by the Free 
Software
@@ -104,12 +104,18 @@ handle_get_token_family_finished (void *cls,
                                  &res.details.ok.description),
         GNUNET_JSON_spec_object_const ("description_i18n",
                                        &res.details.ok.description_i18n),
+        GNUNET_JSON_spec_object_const ("extra_data",
+                                       &res.details.ok.extra_data),
         GNUNET_JSON_spec_timestamp ("valid_after",
                                     &res.details.ok.valid_after),
         GNUNET_JSON_spec_timestamp ("valid_before",
                                     &res.details.ok.valid_before),
         GNUNET_JSON_spec_relative_time ("duation",
                                         &res.details.ok.duration),
+        GNUNET_JSON_spec_relative_time ("validity_granularity",
+                                        &res.details.ok.validity_granularity),
+        GNUNET_JSON_spec_relative_time ("start_offset",
+                                        &res.details.ok.start_offset),
         GNUNET_JSON_spec_string ("kind",
                                  &res.details.ok.kind),
         GNUNET_JSON_spec_uint64 ("issued",
@@ -155,6 +161,7 @@ handle_get_token_family_finished (void *cls,
   }
 }
 
+
 struct TALER_MERCHANT_TokenFamilyGetHandle *
 TALER_MERCHANT_token_family_get (
   struct GNUNET_CURL_Context *ctx,
@@ -199,6 +206,7 @@ TALER_MERCHANT_token_family_get (
   return handle;
 }
 
+
 void
 TALER_MERCHANT_token_family_get_cancel (
   struct TALER_MERCHANT_TokenFamilyGetHandle *handle)
@@ -207,4 +215,4 @@ TALER_MERCHANT_token_family_get_cancel (
     GNUNET_CURL_job_cancel (handle->job);
   GNUNET_free (handle->url);
   GNUNET_free (handle);
-}
\ No newline at end of file
+}
diff --git a/src/lib/merchant_api_post_order_pay.c 
b/src/lib/merchant_api_post_order_pay.c
index d10fc6d7..1996f757 100644
--- a/src/lib/merchant_api_post_order_pay.c
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -520,6 +520,8 @@ TALER_MERCHANT_order_pay_frontend (
                                     &ut->token_sig),
         GNUNET_JSON_pack_data_auto ("token_pub",
                                     &ut->token_pub),
+        GNUNET_JSON_pack_data_auto ("h_issue",
+                                    &ut->issue_pub.public_key->pub_key_hash),
         TALER_JSON_pack_token_issue_sig ("ub_sig",
                                          &ut->ub_sig));
       if (0 !=
diff --git a/src/lib/merchant_api_post_tokenfamilies.c 
b/src/lib/merchant_api_post_tokenfamilies.c
index 95cbd13b..d2c1565b 100644
--- a/src/lib/merchant_api_post_tokenfamilies.c
+++ b/src/lib/merchant_api_post_tokenfamilies.c
@@ -84,8 +84,8 @@ struct TALER_MERCHANT_TokenFamiliesPostHandle
  */
 static void
 handle_post_token_families_finished (void *cls,
-                                    long response_code,
-                                    const void *response)
+                                     long response_code,
+                                     const void *response)
 {
   struct TALER_MERCHANT_TokenFamiliesPostHandle *handle = cls;
   const json_t *json = response;
@@ -98,7 +98,7 @@ handle_post_token_families_finished (void *cls,
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "POST /tokenfamilies completed with response code %u\n",
               (unsigned int) response_code);
-              switch (response_code)
+  switch (response_code)
   {
   case 0:
     hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
@@ -158,6 +158,7 @@ handle_post_token_families_finished (void *cls,
   TALER_MERCHANT_token_families_post_cancel (handle);
 }
 
+
 struct TALER_MERCHANT_TokenFamiliesPostHandle *
 TALER_MERCHANT_token_families_post (
   struct GNUNET_CURL_Context *ctx,
@@ -166,10 +167,12 @@ TALER_MERCHANT_token_families_post (
   const char *name,
   const char *description,
   const json_t *description_i18n,
+  const json_t *extra_data,
   struct GNUNET_TIME_Timestamp valid_after,
   struct GNUNET_TIME_Timestamp valid_before,
   struct GNUNET_TIME_Relative duration,
-  struct GNUNET_TIME_Relative rounding,
+  struct GNUNET_TIME_Relative validity_granularity,
+  struct GNUNET_TIME_Relative start_offset,
   const char *kind,
   TALER_MERCHANT_TokenFamiliesPostCallback cb,
   void *cb_cls)
@@ -187,6 +190,9 @@ TALER_MERCHANT_token_families_post (
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_object_incref ("description_i18n",
                                       (json_t *) description_i18n)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_object_incref ("extra_data",
+                                      (json_t *) extra_data)),
     GNUNET_JSON_pack_allow_null (
       GNUNET_JSON_pack_timestamp ("valid_after",
                                   valid_after)),
@@ -194,8 +200,10 @@ TALER_MERCHANT_token_families_post (
                                 valid_before),
     GNUNET_JSON_pack_time_rel ("duration",
                                duration),
-    GNUNET_JSON_pack_time_rel ("rounding",
-                               rounding),
+    GNUNET_JSON_pack_time_rel ("validity_granularity",
+                               validity_granularity),
+    GNUNET_JSON_pack_time_rel ("start_offset",
+                               start_offset),
     GNUNET_JSON_pack_string ("kind",
                              kind));
   handle = GNUNET_new (struct TALER_MERCHANT_TokenFamiliesPostHandle);
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index b087f070..b0f7aca9 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -14,11 +14,11 @@ check_SCRIPTS = \
   test_merchant_instance_purge.sh \
   test_merchant_product_creation.sh \
   test_merchant_order_creation.sh \
-  test_merchant_transfer_tracking.sh \
   test_merchant_kyc.sh \
   test_merchant_order_autocleanup.sh \
   test_merchant_wirewatch.sh \
-  test-merchant-walletharness.sh
+  test-merchant-walletharness.sh \
+  test_merchant_transfer_tracking.sh
 
 lib_LTLIBRARIES = \
   libtalermerchanttesting.la
@@ -117,6 +117,7 @@ TESTS = \
   $(check_PROGRAMS) \
   $(check_SCRIPTS)
 
+AM_TESTS_ENVIRONMENT=export 
TALER_MERCHANT_PREFIX=$${TALER_MERCHANT_PREFIX:-@libdir@};export 
PATH=$${TALER_MERCHANT_PREFIX:-@prefix@}/bin:$$PATH;
 
 test_merchant_api_twisted_cs_SOURCES = \
   test_merchant_api_twisted.c
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index 483aacce..24118031 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -1697,17 +1697,15 @@ run (void *cls,
       "An upcoming subscription that is not valid yet.",
       NULL,
       /* In one day */
-      GNUNET_TIME_absolute_to_timestamp
-      (
+      GNUNET_TIME_absolute_to_timestamp (
         GNUNET_TIME_absolute_add (
-          GNUNET_TIME_timestamp_get ()
-          .abs_time, GNUNET_TIME_UNIT_DAYS)),
+          GNUNET_TIME_timestamp_get ().abs_time,
+          GNUNET_TIME_UNIT_DAYS)),
       /* In a year */
-      GNUNET_TIME_absolute_to_timestamp
-      (
+      GNUNET_TIME_absolute_to_timestamp (
         GNUNET_TIME_absolute_add (
-          GNUNET_TIME_timestamp_get ()
-          .abs_time, GNUNET_TIME_UNIT_YEARS)),
+          GNUNET_TIME_timestamp_get ().abs_time,
+          GNUNET_TIME_UNIT_YEARS)),
       GNUNET_TIME_UNIT_MONTHS,
       GNUNET_TIME_UNIT_MONTHS,
       "subscription"),
@@ -1715,7 +1713,7 @@ run (void *cls,
       "create-order-with-upcoming-output",
       cred.cfg,
       merchant_url,
-      MHD_HTTP_CONFLICT,
+      MHD_HTTP_OK,
       "create-upcoming-tokenfamily",
       0,
       1,
@@ -1723,40 +1721,43 @@ run (void *cls,
       GNUNET_TIME_UNIT_ZERO_TS,
       GNUNET_TIME_UNIT_FOREVER_TS,
       "EUR:5.0"),
-    TALER_TESTING_cmd_merchant_post_tokenfamilies ("create-tokenfamily",
-                                                   merchant_url,
-                                                   MHD_HTTP_NO_CONTENT,
-                                                   "subscription-1",
-                                                   "Subscription",
-                                                   "A subscription.",
-                                                   NULL,
-                                                   GNUNET_TIME_UNIT_ZERO_TS,
-                                                   
GNUNET_TIME_relative_to_timestamp
-                                                     (GNUNET_TIME_UNIT_YEARS),
-                                                   GNUNET_TIME_UNIT_MONTHS,
-                                                   GNUNET_TIME_UNIT_MONTHS,
-                                                   "subscription"),
-    TALER_TESTING_cmd_merchant_post_orders_choices ("create-order-with-output",
-                                                    cred.cfg,
-                                                    merchant_url,
-                                                    MHD_HTTP_OK,
-                                                    "create-tokenfamily",
-                                                    0,
-                                                    1,
-                                                    "5-output",
-                                                    GNUNET_TIME_UNIT_ZERO_TS,
-                                                    
GNUNET_TIME_UNIT_FOREVER_TS,
-                                                    "EUR:5.0"),
-    TALER_TESTING_cmd_merchant_pay_order_choices ("pay-order-with-output",
-                                                  merchant_url,
-                                                  MHD_HTTP_OK,
-                                                  "create-order-with-output",
-                                                  "withdraw-coin-1",
-                                                  "EUR:5",
-                                                  "EUR:4.99",
-                                                  NULL,
-                                                  0,
-                                                  NULL),
+    TALER_TESTING_cmd_merchant_post_tokenfamilies (
+      "create-tokenfamily",
+      merchant_url,
+      MHD_HTTP_NO_CONTENT,
+      "subscription-1",
+      "Subscription",
+      "A subscription.",
+      NULL,
+      GNUNET_TIME_UNIT_ZERO_TS,
+      GNUNET_TIME_relative_to_timestamp
+        (GNUNET_TIME_UNIT_YEARS),
+      GNUNET_TIME_UNIT_MONTHS,
+      GNUNET_TIME_UNIT_MONTHS,
+      "subscription"),
+    TALER_TESTING_cmd_merchant_post_orders_choices (
+      "create-order-with-output",
+      cred.cfg,
+      merchant_url,
+      MHD_HTTP_OK,
+      "create-tokenfamily",
+      0,
+      1,
+      "5-output",
+      GNUNET_TIME_UNIT_ZERO_TS,
+      GNUNET_TIME_UNIT_FOREVER_TS,
+      "EUR:5.0"),
+    TALER_TESTING_cmd_merchant_pay_order_choices (
+      "pay-order-with-output",
+      merchant_url,
+      MHD_HTTP_OK,
+      "create-order-with-output",
+      "withdraw-coin-1",
+      "EUR:5",
+      "EUR:4.99",
+      NULL,
+      0,
+      NULL),
     TALER_TESTING_cmd_merchant_post_orders_choices (
       "create-order-with-input-and-output",
       cred.cfg,
diff --git a/src/testing/test_merchant_order_creation.sh 
b/src/testing/test_merchant_order_creation.sh
index a566cb7d..d55f7ce1 100755
--- a/src/testing/test_merchant_order_creation.sh
+++ b/src/testing/test_merchant_order_creation.sh
@@ -91,6 +91,7 @@ fi
 # CREATE INSTANCE FOR TESTING
 #
 
+
 echo -n "Configuring merchant instance ..."
 
 STATUS=$(curl -H "Content-Type: application/json" -X POST \
@@ -135,9 +136,9 @@ then
     exit_fail "Expected '200 OK' response. Got instead $STATUS"
 fi
 
-
 echo "Ok"
 
+
 echo -n "Get accounts..."
 STATUS=$(curl http://localhost:9966/private/accounts \
     -w "%{http_code}" -s -o "$LAST_RESPONSE")
@@ -248,10 +249,12 @@ echo "OK"
 # CREATE TOKEN FAMILY AND V1 ORDER WITH CHOICES
 #
 echo -n "Creating token family ..."
-NOW=$(date +%s)
-IN_A_YEAR=$((NOW + 31536000))
+export NOW=$(date +%s)
+export IN_A_YEAR=$((NOW + 31536000))
+export LAST_RESPONSE
+
 STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
-    -d '{"slug":"test-sub","kind":"subscription","description":"Test token 
family","name":"Test 
Subscription","valid_after":{"t_s":'${NOW}'},"valid_before":{"t_s":'${IN_A_YEAR}'},"duration":
 {"d_us": 2592000000000},"rounding": {"d_us": 86400000000}}' \
+    -d '{"slug":"test-sub","kind":"subscription","description":"Test token 
family","name":"Test 
Subscription","valid_after":{"t_s":'${NOW}'},"valid_before":{"t_s":'${IN_A_YEAR}'},"duration":
 {"d_us": 2592000000000},"validity_granularity": {"d_us": 86400000000}}' \
     -w "%{http_code}" -s -o "$LAST_RESPONSE")
 
 if [ "$STATUS" != "204" ]
@@ -262,6 +265,9 @@ 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'}}]}]}}'"
+
 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'}}]}]}}'
 \
diff --git a/src/testing/testing_api_cmd_pay_order.c 
b/src/testing/testing_api_cmd_pay_order.c
index dc27b842..49dd7e81 100644
--- a/src/testing/testing_api_cmd_pay_order.c
+++ b/src/testing/testing_api_cmd_pay_order.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2018, 2020 Taler Systems SA
+  Copyright (C) 2014-2024 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as
@@ -144,14 +144,14 @@ struct PayState
  *
  * @param token_families json object of token families where the key is the 
slug
  * @param slug the slug of the token family
- * @param valid_after the timestamp of the token family
+ * @param key_index index of the key within the token family
  * @param[out] pub the token issue public key of the token family
  * @return #GNUNET_OK on success and #GNUNET_SYSERR if not found
  */
 static enum GNUNET_GenericReturnValue
 find_token_public_key (const json_t *token_families,
                        const char *slug,
-                       struct GNUNET_TIME_Timestamp valid_after,
+                       unsigned int key_index,
                        struct TALER_TokenIssuePublicKey *pub)
 {
   const json_t *tf = json_object_get (token_families, slug);
@@ -161,6 +161,14 @@ find_token_public_key (const json_t *token_families,
                                   &keys),
     GNUNET_JSON_spec_end ()
   };
+  const json_t *key;
+  const char *error_name;
+  unsigned int error_line;
+  struct GNUNET_JSON_Specification ispec[] = {
+    TALER_JSON_spec_token_pub ("public_key",
+                               pub),
+    GNUNET_JSON_spec_end ()
+  };
 
   if (NULL == tf)
   {
@@ -181,77 +189,30 @@ find_token_public_key (const json_t *token_families,
     return GNUNET_SYSERR;
   }
 
+  key = json_array_get (keys,
+                        key_index);
+  if (NULL == key)
   {
-    unsigned int i;
-    const json_t *key;
-
-    json_array_foreach (keys, i, key)
-    {
-      int64_t cipher;
-      struct GNUNET_TIME_Timestamp ivalid_after;
-      struct GNUNET_CRYPTO_BlindSignPublicKey *issue_pub = GNUNET_new (struct
-                                                                       
GNUNET_CRYPTO_BlindSignPublicKey);
-      const char *error_name;
-      unsigned int error_line;
-      struct GNUNET_JSON_Specification ispec[] = {
-        GNUNET_JSON_spec_fixed_auto ("h_pub",
-                                     &issue_pub->pub_key_hash),
-        GNUNET_JSON_spec_rsa_public_key ("rsa_pub",
-                                         &issue_pub->details.rsa_public_key),
-        //   GNUNET_JSON_spec_fixed_auto ("cs_pub",
-        //                               
&key.pub.public_key->details.cs_public_key)),
-        GNUNET_JSON_spec_int64 ("cipher",
-                                &cipher),
-        GNUNET_JSON_spec_timestamp ("valid_after",
-                                    &ivalid_after),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (key,
-                             ispec,
-                             &error_name,
-                             &error_line))
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                    "Failed to parse %s at %u: %s\n",
-                    ispec[error_line].field,
-                    error_line,
-                    error_name);
-        return GNUNET_SYSERR;
-      }
-
-      switch (cipher)
-      {
-      case GNUNET_CRYPTO_BSA_RSA:
-        issue_pub->cipher = GNUNET_CRYPTO_BSA_RSA;
-        break;
-      case GNUNET_CRYPTO_BSA_CS:
-        issue_pub->cipher = GNUNET_CRYPTO_BSA_CS;
-        break;
-      case GNUNET_CRYPTO_BSA_INVALID:
-        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                    "Field 'cipher' invalid in key #%u\n",
-                    i);
-        return GNUNET_SYSERR;
-      }
-
-      /* Compare valid_after to make sure it matches. */
-      if (GNUNET_TIME_timestamp_cmp (valid_after, !=, ivalid_after))
-      {
-        continue;
-      }
-
-      pub->public_key = issue_pub;
-      return GNUNET_OK;
-    }
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Key with index %u for token family '%s' not found\n",
+                key_index,
+                slug);
+    return GNUNET_SYSERR;
   }
-
-  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-              "Key with valid_after '%s' for token family '%s' not found\n",
-              GNUNET_TIME_timestamp2s (valid_after),
-              slug);
-  return GNUNET_SYSERR;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (key,
+                         ispec,
+                         &error_name,
+                         &error_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to parse %s at %u: %s\n",
+                ispec[error_line].field,
+                error_line,
+                error_name);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
 }
 
 
@@ -772,7 +733,7 @@ pay_run (void *cls,
     {
       const char *slug;
       const char *kind;
-      struct GNUNET_TIME_Timestamp valid_after;
+      uint32_t key_index;
       uint32_t count = 1;
       const char *ierror_name = NULL;
       unsigned int ierror_line = 0;
@@ -782,8 +743,8 @@ pay_run (void *cls,
                                  &kind),
         GNUNET_JSON_spec_string ("token_family_slug",
                                  &slug),
-        GNUNET_JSON_spec_timestamp ("valid_after",
-                                    &valid_after),
+        GNUNET_JSON_spec_uint32 ("key_index",
+                                 &key_index),
         GNUNET_JSON_spec_mark_optional (
           GNUNET_JSON_spec_uint32 ("count",
                                    &count),
@@ -820,17 +781,18 @@ pay_run (void *cls,
         struct TALER_MERCHANT_PrivateTokenDetails *details =
           &ps->issued_tokens[ps->num_issued_tokens - count + k];
 
-        if (GNUNET_OK != find_token_public_key (token_families,
-                                                slug,
-                                                valid_after,
-                                                &details->issue_pub))
+        if (GNUNET_OK !=
+            find_token_public_key (token_families,
+                                   slug,
+                                   key_index,
+                                   &details->issue_pub))
         {
           TALER_TESTING_FAIL (is);
         }
 
         /* Only RSA is supported for now. */
-        GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == details->issue_pub.public_key->
-                       cipher);
+        GNUNET_assert (GNUNET_CRYPTO_BSA_RSA ==
+                       details->issue_pub.public_key->cipher);
 
         TALER_token_blind_input_copy (&details->blinding_inputs,
                                       TALER_token_blind_input_rsa_singleton 
());
@@ -883,29 +845,29 @@ pay_run (void *cls,
                                                 &h_proposal))
     TALER_TESTING_FAIL (is);
   ps->h_contract_terms = *h_proposal;
-  ps->oph = TALER_MERCHANT_order_pay (TALER_TESTING_interpreter_get_context (
-                                        is),
-                                      ps->merchant_url,
-                                      ps->session_id,
-                                      h_proposal,
-                                      ps->choice_index,
-                                      &ps->total_amount,
-                                      &max_fee,
-                                      &merchant_pub,
-                                      merchant_sig,
-                                      timestamp,
-                                      refund_deadline,
-                                      pay_deadline,
-                                      &h_wire,
-                                      order_id,
-                                      npay_coins,
-                                      pay_coins,
-                                      len_use_tokens,
-                                      use_tokens,
-                                      len_output_tokens,
-                                      output_tokens,
-                                      &pay_cb,
-                                      ps);
+  ps->oph = TALER_MERCHANT_order_pay (
+    TALER_TESTING_interpreter_get_context (is),
+    ps->merchant_url,
+    ps->session_id,
+    h_proposal,
+    ps->choice_index,
+    &ps->total_amount,
+    &max_fee,
+    &merchant_pub,
+    merchant_sig,
+    timestamp,
+    refund_deadline,
+    pay_deadline,
+    &h_wire,
+    order_id,
+    npay_coins,
+    pay_coins,
+    len_use_tokens,
+    use_tokens,
+    len_output_tokens,
+    output_tokens,
+    &pay_cb,
+    ps);
   GNUNET_array_grow (pay_coins,
                      npay_coins,
                      0);
diff --git a/src/testing/testing_api_cmd_post_tokenfamilies.c 
b/src/testing/testing_api_cmd_post_tokenfamilies.c
index c24b98db..92ed2c77 100644
--- a/src/testing/testing_api_cmd_post_tokenfamilies.c
+++ b/src/testing/testing_api_cmd_post_tokenfamilies.c
@@ -145,6 +145,7 @@ post_tokenfamilies_cb (void *cls,
   TALER_TESTING_interpreter_next (state->is);
 }
 
+
 /**
  * Run the "POST /tokenfamilies" CMD.
  *
@@ -168,16 +169,19 @@ post_tokenfamilies_run (void *cls,
     state->name,
     state->description,
     state->description_i18n,
+    NULL, /* extra data */
     state->valid_after,
     state->valid_before,
     state->duration,
     state->rounding,
+    GNUNET_TIME_UNIT_ZERO, /* start_offset */
     state->kind,
     &post_tokenfamilies_cb,
     state);
   GNUNET_assert (NULL != state->handle);
 }
 
+
 /**
  * Offers information from the "POST /tokenfamilies" CMD state to other
  * commands.
@@ -212,6 +216,7 @@ post_tokenfamilies_traits (void *cls,
                                   index);
 }
 
+
 /**
  * Free the state of a "POST /tokenfamilies" CMD, and possibly
  * cancel a pending operation thereof.
@@ -235,6 +240,7 @@ post_tokenfamilies_cleanup (void *cls,
   GNUNET_free (state);
 }
 
+
 struct TALER_TESTING_Command
 TALER_TESTING_cmd_merchant_post_tokenfamilies (
   const char *label,
@@ -277,4 +283,4 @@ TALER_TESTING_cmd_merchant_post_tokenfamilies (
 
     return cmd;
   }
-}
\ No newline at end of file
+}

-- 
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]