gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated (078f26f0 -> 2eec9cf0)


From: gnunet
Subject: [taler-merchant] branch master updated (078f26f0 -> 2eec9cf0)
Date: Fri, 22 Dec 2023 17:24:59 +0100

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

grothoff pushed a change to branch master
in repository merchant.

    from 078f26f0 -currency code was removed from currency specifications
     new d9dee21a create token_families and spent_tokens tables
     new 2c9864ce gitignore .DS_Store files
     new af0d2a19 create separate merchant_token_keys table
     new 540c0dca detail structs for token family, keys and spent tokens
     new b0017f19 remove unused enum
     new 6ad92a2d review feedback
     new f13c141f db: better naming for validity period
     new 20632c6a db plugin: better naming for validity
     new 094327bc POST /tokenfamilies endpoint
     new 06998718 list, update, delete token families
     new 129380c7 start with post orders handler refactoring
     new 5fa8678d set next phase at the end of serialize phase
     new ebb6c68e fix order serialization so test are passing
     new bb2b8f99 refactor phases and parsing logic
     new 8b66d457 add TALER_MERCHANTDB_token_family_details_free
     new a7c9f6d4 remove init lines
     new bb45bac2 products should be optional
     new e7a8f953 -nicer logging
     new 2eec9cf0 -fix expected HTTP status codes

The 19 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |    1 +
 src/backend/Makefile.am                            |   10 +
 src/backend/taler-merchant-httpd.c                 |   41 +
 ...ant-httpd_private-delete-token-families-SLUG.c} |   35 +-
 ...ant-httpd_private-delete-token-families-SLUG.h} |   21 +-
 .../taler-merchant-httpd_private-get-templates.c   |    3 +-
 ...erchant-httpd_private-get-token-families-SLUG.c |  104 ++
 ...rchant-httpd_private-get-token-families-SLUG.h} |   21 +-
 ...ler-merchant-httpd_private-get-token-families.c |   87 ++
 ...er-merchant-httpd_private-get-token-families.h} |   17 +-
 ...aler-merchant-httpd_private-patch-products-ID.h |    2 +-
 ...hant-httpd_private-patch-token-families-SLUG.c} |  111 +-
 ...hant-httpd_private-patch-token-families-SLUG.h} |   18 +-
 .../taler-merchant-httpd_private-post-orders.c     | 1094 +++++++++++---------
 ...er-merchant-httpd_private-post-token-families.c |  244 +++++
 ...r-merchant-httpd_private-post-token-families.h} |   18 +-
 src/backenddb/Makefile.am                          |    5 +
 src/backenddb/merchant-0002.sql                    |   99 ++
 src/backenddb/merchantdb_helper.c                  |   10 +
 ...g_delete_webhook.c => pg_delete_token_family.c} |   29 +-
 ...lete_login_token.h => pg_delete_token_family.h} |   33 +-
 src/backenddb/pg_insert_token_family.c             |   82 ++
 ...lete_login_token.h => pg_insert_token_family.h} |   27 +-
 src/backenddb/pg_lookup_token_families.c           |  150 +++
 ...account_by_uri.h => pg_lookup_token_families.h} |   25 +-
 src/backenddb/pg_lookup_token_family.c             |  121 +++
 ..._pending_webhook.h => pg_lookup_token_family.h} |   24 +-
 ...g_update_webhook.c => pg_update_token_family.c} |   55 +-
 ...kup_order_status.h => pg_update_token_family.h} |   37 +-
 src/backenddb/plugin_merchantdb_postgres.c         |   18 +
 src/include/taler_merchantdb_lib.h                 |   10 +
 src/include/taler_merchantdb_plugin.h              |  223 ++++
 src/testing/test_merchant_api.c                    |    6 +-
 src/testing/testing_api_cmd_forget_order.c         |    7 +-
 src/testing/testing_api_cmd_post_templates.c       |    9 +-
 35 files changed, 2089 insertions(+), 708 deletions(-)
 copy src/backend/{taler-merchant-httpd_private-delete-webhooks-ID.c => 
taler-merchant-httpd_private-delete-token-families-SLUG.c} (65%)
 copy src/backend/{taler-merchant-httpd_private-delete-account-ID.h => 
taler-merchant-httpd_private-delete-token-families-SLUG.h} (60%)
 create mode 100644 
src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
 copy src/backend/{taler-merchant-httpd_private-delete-account-ID.h => 
taler-merchant-httpd_private-get-token-families-SLUG.h} (61%)
 create mode 100644 
src/backend/taler-merchant-httpd_private-get-token-families.c
 copy src/backend/{taler-merchant-httpd_private-delete-account-ID.h => 
taler-merchant-httpd_private-get-token-families.h} (71%)
 copy src/backend/{taler-merchant-httpd_private-patch-accounts-ID.c => 
taler-merchant-httpd_private-patch-token-families-SLUG.c} (50%)
 copy src/backend/{taler-merchant-httpd_private-patch-accounts-ID.h => 
taler-merchant-httpd_private-patch-token-families-SLUG.h} (62%)
 create mode 100644 
src/backend/taler-merchant-httpd_private-post-token-families.c
 copy src/backend/{taler-merchant-httpd_private-patch-accounts-ID.h => 
taler-merchant-httpd_private-post-token-families.h} (64%)
 copy src/backenddb/{pg_delete_webhook.c => pg_delete_token_family.c} (68%)
 copy src/backenddb/{pg_delete_login_token.h => pg_delete_token_family.h} (58%)
 create mode 100644 src/backenddb/pg_insert_token_family.c
 copy src/backenddb/{pg_delete_login_token.h => pg_insert_token_family.h} (58%)
 create mode 100644 src/backenddb/pg_lookup_token_families.c
 copy src/backenddb/{pg_select_account_by_uri.h => pg_lookup_token_families.h} 
(60%)
 create mode 100644 src/backenddb/pg_lookup_token_family.c
 copy src/backenddb/{pg_delete_pending_webhook.h => pg_lookup_token_family.h} 
(54%)
 copy src/backenddb/{pg_update_webhook.c => pg_update_token_family.c} (53%)
 copy src/backenddb/{pg_lookup_order_status.h => pg_update_token_family.h} (51%)

diff --git a/.gitignore b/.gitignore
index b8f96343..c11acb71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 .vscode
+.DS_Store
 *~
 *Makefile.in
 !src/mitm/*.in
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 4a25785c..7978258d 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -53,6 +53,8 @@ taler_merchant_httpd_SOURCES = \
     taler-merchant-httpd_private-delete-reserves-ID.h \
   taler-merchant-httpd_private-delete-templates-ID.c \
     taler-merchant-httpd_private-delete-templates-ID.h \
+  taler-merchant-httpd_private-delete-token-families-SLUG.c \
+    taler-merchant-httpd_private-delete-token-families-SLUG.h \
   taler-merchant-httpd_private-delete-transfers-ID.c \
     taler-merchant-httpd_private-delete-transfers-ID.h \
   taler-merchant-httpd_private-delete-webhooks-ID.c \
@@ -89,6 +91,10 @@ taler_merchant_httpd_SOURCES = \
     taler-merchant-httpd_private-get-templates.h \
   taler-merchant-httpd_private-get-templates-ID.c \
     taler-merchant-httpd_private-get-templates-ID.h \
+  taler-merchant-httpd_private-get-token-families.c \
+    taler-merchant-httpd_private-get-token-families.h \
+  taler-merchant-httpd_private-get-token-families-SLUG.c \
+    taler-merchant-httpd_private-get-token-families-SLUG.h \
   taler-merchant-httpd_private-get-webhooks.c \
     taler-merchant-httpd_private-get-webhooks.h \
   taler-merchant-httpd_private-get-webhooks-ID.c \
@@ -105,6 +111,8 @@ taler_merchant_httpd_SOURCES = \
     taler-merchant-httpd_private-patch-products-ID.h \
   taler-merchant-httpd_private-patch-templates-ID.c \
     taler-merchant-httpd_private-patch-templates-ID.h \
+  taler-merchant-httpd_private-patch-token-families-SLUG.c \
+    taler-merchant-httpd_private-patch-token-families-SLUG.h \
   taler-merchant-httpd_private-patch-webhooks-ID.c \
     taler-merchant-httpd_private-patch-webhooks-ID.h \
   taler-merchant-httpd_private-post-account.c \
@@ -131,6 +139,8 @@ taler_merchant_httpd_SOURCES = \
     taler-merchant-httpd_private-post-reserves-ID-authorize-reward.h \
   taler-merchant-httpd_private-post-templates.c \
     taler-merchant-httpd_private-post-templates.h \
+  taler-merchant-httpd_private-post-token-families.c \
+    taler-merchant-httpd_private-post-token-families.h \
   taler-merchant-httpd_private-post-transfers.c \
     taler-merchant-httpd_private-post-transfers.h \
   taler-merchant-httpd_private-post-webhooks.c \
diff --git a/src/backend/taler-merchant-httpd.c 
b/src/backend/taler-merchant-httpd.c
index 3b78677a..83390887 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -41,6 +41,7 @@
 #include "taler-merchant-httpd_private-delete-otp-devices-ID.h"
 #include "taler-merchant-httpd_private-delete-reserves-ID.h"
 #include "taler-merchant-httpd_private-delete-templates-ID.h"
+#include "taler-merchant-httpd_private-delete-token-families-SLUG.h"
 #include "taler-merchant-httpd_private-delete-transfers-ID.h"
 #include "taler-merchant-httpd_private-delete-webhooks-ID.h"
 #include "taler-merchant-httpd_private-get-accounts.h"
@@ -60,6 +61,8 @@
 #include "taler-merchant-httpd_private-get-rewards.h"
 #include "taler-merchant-httpd_private-get-templates.h"
 #include "taler-merchant-httpd_private-get-templates-ID.h"
+#include "taler-merchant-httpd_private-get-token-families.h"
+#include "taler-merchant-httpd_private-get-token-families-SLUG.h"
 #include "taler-merchant-httpd_private-get-transfers.h"
 #include "taler-merchant-httpd_private-get-webhooks.h"
 #include "taler-merchant-httpd_private-get-webhooks-ID.h"
@@ -69,6 +72,7 @@
 #include "taler-merchant-httpd_private-patch-otp-devices-ID.h"
 #include "taler-merchant-httpd_private-patch-products-ID.h"
 #include "taler-merchant-httpd_private-patch-templates-ID.h"
+#include "taler-merchant-httpd_private-patch-token-families-SLUG.h"
 #include "taler-merchant-httpd_private-patch-webhooks-ID.h"
 #include "taler-merchant-httpd_private-post-account.h"
 #include "taler-merchant-httpd_private-post-instances.h"
@@ -82,6 +86,7 @@
 #include "taler-merchant-httpd_private-post-reserves.h"
 #include "taler-merchant-httpd_private-post-reserves-ID-authorize-reward.h"
 #include "taler-merchant-httpd_private-post-templates.h"
+#include "taler-merchant-httpd_private-post-token-families.h"
 #include "taler-merchant-httpd_private-post-transfers.h"
 #include "taler-merchant-httpd_private-post-webhooks.h"
 #include "taler-merchant-httpd_post-orders-ID-abort.h"
@@ -1300,6 +1305,42 @@ url_handler (void *cls,
       .method = MHD_HTTP_METHOD_DELETE,
       .handler = &TMH_private_delete_instances_ID_token,
     },
+    /* GET /tokenfamilies: */
+    {
+      .url_prefix = "/tokenfamilies",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_tokenfamilies
+    },
+    /* POST /tokenfamilies: */
+    {
+      .url_prefix = "/tokenfamilies",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_private_post_token_families
+    },
+    /* GET /tokenfamilies/$SLUG/: */
+    {
+      .url_prefix = "/tokenfamilies/",
+      .method = MHD_HTTP_METHOD_GET,
+      .have_id_segment = true,
+      .allow_deleted_instance = true,
+      .handler = &TMH_private_get_tokenfamilies_SLUG
+    },
+    /* DELETE /tokenfamilies/$SLUG/: */
+    {
+      .url_prefix = "/tokenfamilies/",
+      .method = MHD_HTTP_METHOD_DELETE,
+      .have_id_segment = true,
+      .allow_deleted_instance = true,
+      .handler = &TMH_private_delete_token_families_SLUG
+    },
+    /* PATCH /tokenfamilies/$SLUG/: */
+    {
+      .url_prefix = "/tokenfamilies/",
+      .method = MHD_HTTP_METHOD_PATCH,
+      .have_id_segment = true,
+      .allow_deleted_instance = true,
+      .handler = &TMH_private_patch_token_family_SLUG,
+    },
     {
       .url_prefix = NULL
     }
diff --git a/src/backend/taler-merchant-httpd_private-delete-webhooks-ID.c 
b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c
similarity index 65%
copy from src/backend/taler-merchant-httpd_private-delete-webhooks-ID.c
copy to src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c
index e8e2d283..de7b6471 100644
--- a/src/backend/taler-merchant-httpd_private-delete-webhooks-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2022 Taler Systems SA
+  (C) 2023 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
@@ -14,17 +14,18 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file taler-merchant-httpd_private-delete-webhooks-ID.c
- * @brief implement DELETE /webhooks/$ID
- * @author Priscilla HUANG
+ * @file taler-merchant-httpd_private-delete-token-families-SLUG.c
+ * @brief implement DELETE /tokenfamilies/$SLUG
+ * @author Christian Blättler
  */
 #include "platform.h"
-#include "taler-merchant-httpd_private-delete-webhooks-ID.h"
+#include "taler-merchant-httpd_private-delete-token-families-SLUG.h"
+#include <gnunet/gnunet_db_lib.h>
 #include <taler/taler_json_lib.h>
 
 
 /**
- * Handle a DELETE "/webhooks/$ID" request.
+ * Handle a DELETE "/tokenfamilies/$SLUG" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -32,9 +33,9 @@
  * @return MHD result code
  */
 MHD_RESULT
-TMH_private_delete_webhooks_ID (const struct TMH_RequestHandler *rh,
-                                struct MHD_Connection *connection,
-                                struct TMH_HandlerContext *hc)
+TMH_private_delete_token_families_SLUG (const struct TMH_RequestHandler *rh,
+                                        struct MHD_Connection *connection,
+                                        struct TMH_HandlerContext *hc)
 {
   struct TMH_MerchantInstance *mi = hc->instance;
   enum GNUNET_DB_QueryStatus qs;
@@ -42,27 +43,23 @@ TMH_private_delete_webhooks_ID (const struct 
TMH_RequestHandler *rh,
   (void) rh;
   GNUNET_assert (NULL != mi);
   GNUNET_assert (NULL != hc->infix);
-  qs = TMH_db->delete_webhook (TMH_db->cls,
-                               mi->settings.id,
-                               hc->infix);
+  qs = TMH_db->delete_token_family (TMH_db->cls,
+                                    mi->settings.id,
+                                    hc->infix);
   switch (qs)
   {
   case GNUNET_DB_STATUS_HARD_ERROR:
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                                        TALER_EC_GENERIC_DB_STORE_FAILED,
-                                       "delete_webhook");
+                                       "delete_token_family");
   case GNUNET_DB_STATUS_SOFT_ERROR:
     GNUNET_break (0);
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                                        
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                                       "delete_webhook (soft)");
+                                       "delete_token_family (soft)");
   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       
TALER_EC_MERCHANT_GENERIC_WEBHOOK_UNKNOWN,
-                                       hc->infix);
   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     return TALER_MHD_reply_static (connection,
                                    MHD_HTTP_NO_CONTENT,
@@ -75,4 +72,4 @@ TMH_private_delete_webhooks_ID (const struct 
TMH_RequestHandler *rh,
 }
 
 
-/* end of taler-merchant-httpd_private-delete-webhooks-ID.c */
+/* end of taler-merchant-httpd_private-delete-token-families-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-account-ID.h 
b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h
similarity index 60%
copy from src/backend/taler-merchant-httpd_private-delete-account-ID.h
copy to src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h
index b9004b9f..e8b72fc6 100644
--- a/src/backend/taler-merchant-httpd_private-delete-account-ID.h
+++ b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h
@@ -14,18 +14,18 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file taler-merchant-httpd_private-delete-account-ID.h
- * @brief implement DELETE /account/$PAYTO
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-delete-token-families-SLUG.h
+ * @brief implement DELETE /tokenfamilies/$SLUG/
+ * @author Christian Blättler
  */
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_TOKEN_FAMILIES_SLUG_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_TOKEN_FAMILIES_SLUG_H
 
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Handle a DELETE "/private/account/$H_WIRE" request.
+ * Handle a DELETE "/tokenfamilies/$SLUG" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -33,10 +33,9 @@
  * @return MHD result code
  */
 MHD_RESULT
-TMH_private_delete_account_ID (const struct TMH_RequestHandler *rh,
-                               struct MHD_Connection *connection,
-                               struct TMH_HandlerContext *hc);
+TMH_private_delete_token_families_SLUG (const struct TMH_RequestHandler *rh,
+                                        struct MHD_Connection *connection,
+                                        struct TMH_HandlerContext *hc);
 
-
-/* end of taler-merchant-httpd_private-delete-account-ID.h */
+/* end of taler-merchant-httpd_private-delete-token-families-SLUG.h */
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-templates.c 
b/src/backend/taler-merchant-httpd_private-get-templates.c
index e59e47c9..d0bec884 100644
--- a/src/backend/taler-merchant-httpd_private-get-templates.c
+++ b/src/backend/taler-merchant-httpd_private-get-templates.c
@@ -40,8 +40,7 @@ add_template (void *cls,
                  json_array_append_new (
                    pa,
                    GNUNET_JSON_PACK (
-                     GNUNET_JSON_pack_string ("template_id",
-                                              template_id),
+                     GNUNET_JSON_pack_string ("template_id", template_id),
                      GNUNET_JSON_pack_string ("template_description",
                                               template_description))));
 }
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
new file mode 100644
index 00000000..b7c8ab4d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
@@ -0,0 +1,104 @@
+/*
+  This file is part of TALER
+  (C) 2023 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
+  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 taler-merchant-httpd_private-get-token-families-SLUG.c
+ * @brief implement GET /tokenfamilies/$SLUG/
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-token-families-SLUG.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/tokenfamilies/$SLUG" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh,
+                                    struct MHD_Connection *connection,
+                                    struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  struct TALER_MERCHANTDB_TokenFamilyDetails details = { 0 };
+  enum GNUNET_DB_QueryStatus status;
+
+  GNUNET_assert (NULL != mi);
+  status = TMH_db->lookup_token_family (TMH_db->cls,
+                                        mi->settings.id,
+                                        hc->infix,
+                                        &details);
+  if (0 > status)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       "lookup_token_family");
+  }
+  if (0 == status)
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       
TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
+                                       hc->infix);
+  }
+  {
+    char *kind = NULL;
+    if (TALER_MERCHANTDB_TFK_Subscription == details.kind)
+    {
+      kind = GNUNET_strdup ("subscription");
+    }
+    else if (TALER_MERCHANTDB_TFK_Discount == details.kind)
+    {
+      kind = GNUNET_strdup ("discount");
+    }
+    else
+    {
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         // TODO: What error code to use here?
+                                         
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                                         "invalid_token_family_kind");
+    }
+
+    MHD_RESULT result;
+
+    result = TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_OK,
+      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_free (details.name);
+    GNUNET_free (details.description);
+    GNUNET_free (kind);
+    return result;
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-get-products-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-account-ID.h 
b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h
similarity index 61%
copy from src/backend/taler-merchant-httpd_private-delete-account-ID.h
copy to src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h
index b9004b9f..a7b02d8f 100644
--- a/src/backend/taler-merchant-httpd_private-delete-account-ID.h
+++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h
@@ -14,18 +14,18 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file taler-merchant-httpd_private-delete-account-ID.h
- * @brief implement DELETE /account/$PAYTO
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-get-token-families-SLUG.h
+ * @brief implement GET /tokenfamilies/$SLUG/
+ * @author Christian Blättler
  */
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_SLUG_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_SLUG_H
 
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Handle a DELETE "/private/account/$H_WIRE" request.
+ * Handle a GET "/tokenfamilies/$SLUG" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -33,10 +33,9 @@
  * @return MHD result code
  */
 MHD_RESULT
-TMH_private_delete_account_ID (const struct TMH_RequestHandler *rh,
-                               struct MHD_Connection *connection,
-                               struct TMH_HandlerContext *hc);
+TMH_private_get_tokenfamilies_SLUG (const struct TMH_RequestHandler *rh,
+                                    struct MHD_Connection *connection,
+                                    struct TMH_HandlerContext *hc);
 
-
-/* end of taler-merchant-httpd_private-delete-account-ID.h */
+/* end of taler-merchant-httpd_private-get-token-families-SLUG.h */
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families.c 
b/src/backend/taler-merchant-httpd_private-get-token-families.c
new file mode 100644
index 00000000..003e2966
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families.c
@@ -0,0 +1,87 @@
+/*
+  This file is part of TALER
+  (C) 2023 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
+  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 taler-merchant-httpd_private-get-token-families.c
+ * @brief implement GET /tokenfamilies
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-token-families.h"
+
+
+
+/**
+ * Add token family details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param slug slug of the token family
+ * @param name name of the token family
+ * @param valid_after start time of the token family's validity period
+ * @param valid_before end time of the token family's validity period
+ */
+static void
+add_token_family (void *cls,
+                  const char *slug,
+                  const char *name,
+                  struct GNUNET_TIME_Timestamp valid_after,
+                  struct GNUNET_TIME_Timestamp valid_before,
+                  const char *kind)
+{
+  json_t *pa = cls;
+
+  GNUNET_assert (0 ==
+                  json_array_append_new (
+                    pa,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_string ("slug", slug),
+                      GNUNET_JSON_pack_string ("name", name),
+                      GNUNET_JSON_pack_timestamp ("valid_after", valid_after),
+                      GNUNET_JSON_pack_timestamp ("valid_before", 
valid_before),
+                      GNUNET_JSON_pack_string ("kind", kind))));
+}
+
+
+MHD_RESULT
+TMH_private_get_tokenfamilies (const struct TMH_RequestHandler *rh,
+                               struct MHD_Connection *connection,
+                               struct TMH_HandlerContext *hc)
+{
+  json_t *families;
+  enum GNUNET_DB_QueryStatus qs;
+
+  families = json_array ();
+  GNUNET_assert (NULL != families);
+  qs = TMH_db->lookup_token_families (TMH_db->cls,
+                                      hc->instance->settings.id,
+                                      &add_token_family,
+                                      families);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    json_decref (families);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       NULL);
+  }
+  return TALER_MHD_REPLY_JSON_PACK (connection,
+                                    MHD_HTTP_OK,
+                                    GNUNET_JSON_pack_array_steal 
("token_families",
+                                                                  families));
+}
+
+
+/* end of taler-merchant-httpd_private-get-token-families.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-account-ID.h 
b/src/backend/taler-merchant-httpd_private-get-token-families.h
similarity index 71%
copy from src/backend/taler-merchant-httpd_private-delete-account-ID.h
copy to src/backend/taler-merchant-httpd_private-get-token-families.h
index b9004b9f..a02a42b0 100644
--- a/src/backend/taler-merchant-httpd_private-delete-account-ID.h
+++ b/src/backend/taler-merchant-httpd_private-get-token-families.h
@@ -14,18 +14,18 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file taler-merchant-httpd_private-delete-account-ID.h
- * @brief implement DELETE /account/$PAYTO
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-get-token-families.h
+ * @brief implement GET /tokenfamilies
+ * @author Christian Blättler
  */
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ACCOUNT_ID_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_H
 
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Handle a DELETE "/private/account/$H_WIRE" request.
+ * Handle a GET "/tokenfamilies" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -33,10 +33,9 @@
  * @return MHD result code
  */
 MHD_RESULT
-TMH_private_delete_account_ID (const struct TMH_RequestHandler *rh,
+TMH_private_get_tokenfamilies (const struct TMH_RequestHandler *rh,
                                struct MHD_Connection *connection,
                                struct TMH_HandlerContext *hc);
 
-
-/* end of taler-merchant-httpd_private-delete-account-ID.h */
+/* end of taler-merchant-httpd_private-get-token-families.h */
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.h 
b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
index e7f8fcfd..9ce0a7ae 100644
--- a/src/backend/taler-merchant-httpd_private-patch-products-ID.h
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
@@ -19,7 +19,7 @@
 
 /**
  * @file taler-merchant-httpd_private-patch-products-ID.h
- * @brief implementing POST /products request handling
+ * @brief implementing PATCH /products/$ID request handling
  * @author Christian Grothoff
  */
 #ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_PRODUCTS_ID_H
diff --git a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.c 
b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
similarity index 50%
copy from src/backend/taler-merchant-httpd_private-patch-accounts-ID.c
copy to src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
index dd18281f..8d34b2ff 100644
--- a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
@@ -18,18 +18,23 @@
 */
 
 /**
- * @file taler-merchant-httpd_private-patch-accounts-ID.c
- * @brief implementing PATCH /accounts/$ID request handling
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-patch-token-families-SLUG.c
+ * @brief implementing PATCH /tokenfamilies/$SLUG request handling
+ * @author Christian Blättler
  */
 #include "platform.h"
-#include "taler-merchant-httpd_private-patch-accounts-ID.h"
+#include "taler-merchant-httpd_private-patch-token-families-SLUG.h"
 #include "taler-merchant-httpd_helper.h"
 #include <taler/taler_json_lib.h>
 
 
 /**
- * PATCH configuration of an existing instance, given its configuration.
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * Handle a PATCH "/tokenfamilies/$slug" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -37,42 +42,34 @@
  * @return MHD result code
  */
 MHD_RESULT
-TMH_private_patch_accounts_ID (const struct TMH_RequestHandler *rh,
-                                struct MHD_Connection *connection,
-                                struct TMH_HandlerContext *hc)
+TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh,
+                                     struct MHD_Connection *connection,
+                                     struct TMH_HandlerContext *hc)
 {
   struct TMH_MerchantInstance *mi = hc->instance;
-  const char *h_wire_s = hc->infix;
+  const char *slug = hc->infix;
+  struct TALER_MERCHANTDB_TokenFamilyDetails details = {0};
   enum GNUNET_DB_QueryStatus qs;
-  const json_t *cfc;
-  const char *cfu;
-  struct TALER_MerchantWireHashP h_wire;
   struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("name",
+                             (const char **) &details.name),
+    GNUNET_JSON_spec_string ("description",
+                             (const char **) &details.description),
     GNUNET_JSON_spec_mark_optional (
-      TALER_JSON_spec_web_url ("credit_facade_url",
-                               &cfu),
-      NULL),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_object_const ("credit_facade_credentials",
-                                     &cfc),
+      GNUNET_JSON_spec_json ("description_i18n",
+                             &details.description_i18n),
       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 ()
   };
 
   GNUNET_assert (NULL != mi);
-  GNUNET_assert (NULL != h_wire_s);
-  if (GNUNET_OK !=
-      GNUNET_STRINGS_string_to_data (h_wire_s,
-                                     strlen (h_wire_s),
-                                     &h_wire,
-                                     sizeof (h_wire)))
-  {
-    GNUNET_break_op (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_MERCHANT_GENERIC_H_WIRE_MALFORMED,
-                                       h_wire_s);
-  }
+  GNUNET_assert (NULL != slug);
   {
     enum GNUNET_GenericReturnValue res;
 
@@ -84,12 +81,39 @@ TMH_private_patch_accounts_ID (const struct 
TMH_RequestHandler *rh,
              ? MHD_YES
              : MHD_NO;
   }
-  
-  qs = TMH_db->update_account (TMH_db->cls,
-                               mi->settings.id,
-                               &h_wire,
-                               cfu,
-                               cfc);
+
+  struct GNUNET_TIME_Relative 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 (0 == validity.rel_value_us)
+  {
+    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");
+  }
+
+  if (NULL == details.description_i18n)
+    details.description_i18n = json_object ();
+
+  if (! TALER_JSON_check_i18n (details.description_i18n))
+  {
+    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,
+                                       "description_i18n");
+  }
+
+  qs = TMH_db->update_token_family (TMH_db->cls,
+                                    mi->settings.id,
+                                    slug,
+                                    &details);
   {
     MHD_RESULT ret = MHD_NO;
 
@@ -100,7 +124,7 @@ TMH_private_patch_accounts_ID (const struct 
TMH_RequestHandler *rh,
       ret = TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_STORE_FAILED,
-                                        "update_account");
+                                        NULL);
       break;
     case GNUNET_DB_STATUS_SOFT_ERROR:
       GNUNET_break (0);
@@ -110,10 +134,11 @@ TMH_private_patch_accounts_ID (const struct 
TMH_RequestHandler *rh,
                                         "unexpected serialization problem");
       break;
     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         
TALER_EC_MERCHANT_GENERIC_ACCOUNT_UNKNOWN,
-                                         h_wire_s);
+      // TODO: Add error code for token family not found
+      ret = TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       0,
+                                       slug);
       break;
     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
       ret = TALER_MHD_reply_static (connection,
@@ -129,4 +154,4 @@ TMH_private_patch_accounts_ID (const struct 
TMH_RequestHandler *rh,
 }
 
 
-/* end of taler-merchant-httpd_private-patch-accounts-ID.c */
+/* end of taler-merchant-httpd_private-patch-token-families-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.h 
b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
similarity index 62%
copy from src/backend/taler-merchant-httpd_private-patch-accounts-ID.h
copy to src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
index 752fb958..87ad86b3 100644
--- a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.h
+++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
@@ -18,17 +18,17 @@
 */
 
 /**
- * @file taler-merchant-httpd_private-patch-accounts-ID.h
- * @brief implementing PATCH /accounts request handling
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-patch-token-families-SLUG.h
+ * @brief implementing PATCH /tokenfamilies/$SLUG request handling
+ * @author Christian Blättler
  */
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_ACCOUNTS_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_ACCOUNTS_ID_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_TOKEN_FAMILIES_SLUG_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_TOKEN_FAMILIES_SLUG_H
 #include "taler-merchant-httpd.h"
 
 
 /**
- * PATCH configuration of an existing instance, given its configuration.
+ * Handle a PATCH "/tokenfamilies/$slug" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -36,8 +36,8 @@
  * @return MHD result code
  */
 MHD_RESULT
-TMH_private_patch_accounts_ID (const struct TMH_RequestHandler *rh,
-                               struct MHD_Connection *connection,
-                               struct TMH_HandlerContext *hc);
+TMH_private_patch_token_family_SLUG (const struct TMH_RequestHandler *rh,
+                                     struct MHD_Connection *connection,
+                                     struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c 
b/src/backend/taler-merchant-httpd_private-post-orders.c
index 31b99536..08522f28 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -24,7 +24,11 @@
  * @author Marcello Stanisci
  */
 #include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
 #include <jansson.h>
+#include <string.h>
 #include <taler/taler_signatures.h>
 #include <taler/taler_json_lib.h>
 #include "taler-merchant-httpd_private-post-orders.h"
@@ -132,158 +136,327 @@ struct RekeyExchange
  */
 struct OrderContext
 {
-
   /**
-   * Connection of the request.
+   * Information set in the ORDER_PHASE_PARSE_REQUEST phase.
    */
-  struct MHD_Connection *connection;
+  struct {
+    /**
+     * Order field of the request
+     */
+    json_t *order;
 
-  /**
-   * Kept in a DLL while suspended.
-   */
-  struct OrderContext *next;
+    /**
+    * Set to how long refunds will be allowed.
+    */
+    struct GNUNET_TIME_Relative refund_delay;
 
-  /**
-   * Kept in a DLL while suspended.
-   */
-  struct OrderContext *prev;
+    /**
+     * RFC8905 payment target type to find a matching merchant account
+     */
+    const char *payment_target;
 
-  /**
-   * Handler context for the request.
-   */
-  struct TMH_HandlerContext *hc;
+    /**
+     * Shared key to use with @e pos_algorithm.
+     */
+    const char *pos_key;
 
-  /**
-   * Hash of the POST request data, used to detect
-   * idempotent requests.
-   */
-  struct TALER_MerchantPostDataHashP h_post_data;
+    /**
+     * Selected algorithm (by template) when we are to
+     * generate an OTP code for payment confirmation.
+     */
+    enum TALER_MerchantConfirmationAlgorithm pos_algorithm;
 
-  /**
-   * Payment deadline.
-   */
-  struct GNUNET_TIME_Timestamp pay_deadline;
+    /**
+     * Hash of the POST request data, used to detect
+     * idempotent requests.
+     */
+    struct TALER_MerchantPostDataHashP h_post_data;
 
-  /**
-   * Set to how long refunds will be allowed.
-   */
-  struct GNUNET_TIME_Relative refund_delay;
+    /**
+     * Length of the @e inventory_products array.
+     */
+    unsigned int inventory_products_length;
 
-  /**
-   * Order we are building (modified as we process
-   * the request).
-   */
-  json_t *order;
+    /**
+     * Specifies that some products are to be included in the
+     * order from the inventory. For these inventory management
+     * is performed (so the products must be in stock).
+     */
+    struct InventoryProduct *inventory_products;
 
-  /**
-   * Maximum fee for @e order based on STEFAN curves.
-   * Used to set @e max_fee if not provided as part of
-   * @e order.
-   */
-  struct TALER_Amount max_stefan_fee;
+    /**
+     * Length of the @e uuids array.
+     */
+    unsigned int uuids_length;
 
-  /**
-   * Maximum fee as given by the client request.
-   */
-  struct TALER_Amount max_fee;
+    /**
+     * array of UUIDs used to reserve products from @a inventory_products.
+     */
+    struct GNUNET_Uuid *uuids;
 
-  /**
-   * Gross amount value of the contract. Used to
-   * compute @e max_stefan_fee.
-   */
-  struct TALER_Amount brutto;
+    /**
+     * Claim token for the request.
+     */
+    struct TALER_ClaimTokenP claim_token;
 
-  /**
-   * Array of exchanges we find acceptable for this
-   * order.
-   */
-  json_t *exchanges;
+  } parse_request;
 
-  /**
-   * RFC8905 payment target type to find a matching merchant account
-   */
-  const char *payment_target;
 
   /**
-   * Wire method (and our bank account) we have selected
-   * to be included for this order.
+   * Information set in the ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
    */
-  const struct TMH_WireMethod *wm;
+  struct {
+    /**
+    * Wire method (and our bank account) we have selected
+    * to be included for this order.
+    */
+    const struct TMH_WireMethod *wm;
+  } add_payment_details;
 
   /**
-   * Forced requests to /keys to update our exchange
-   * information.
+   * Information set in the ORDER_PHASE_PARSE_ORDER phase.
    */
-  struct RekeyExchange *pending_reload_head;
+  struct {
+    /**
+     * Our order ID.
+     */
+    const char *order_id;
 
-  /**
-   * Forced requests to /keys to update our exchange
-   * information.
-   */
-  struct RekeyExchange *pending_reload_tail;
+    /**
+     * Summary of the order.
+     */
+    const char *summary;
+
+    /**
+     * Internationalized summary.
+     */
+    json_t *summary_i18n;
+
+    /**
+     * URL where the same contract could be ordered again (if available).
+     */
+    const char *public_reorder_url;
+
+    /**
+     * URL that will show that the order was successful
+     * after it has been paid for.
+     */
+    const char *fulfillment_url;
+
+    /**
+     * Message shown to the customer after paying for the order.
+     * Either fulfillment_url or fulfillment_message must be specified.
+     */
+    const char *fulfillment_message;
+
+    /**
+     * Map from IETF BCP 47 language tags to localized fulfillment messages.
+     */
+    json_t *fulfillment_message_i18n;
+
+    /**
+     * Merchant base URL.
+     */
+    const char *merchant_base_url;
+
+    /**
+     * Timestamp of the order.
+     */
+    struct GNUNET_TIME_Timestamp timestamp;
+
+    /**
+     * Deadline for refunds.
+     */
+    struct GNUNET_TIME_Timestamp refund_deadline;
+
+    /**
+     * Payment deadline.
+     */
+    struct GNUNET_TIME_Timestamp pay_deadline;
+
+    /**
+     * Wire transfer deadline.
+     */
+    struct GNUNET_TIME_Timestamp wire_deadline;
+
+    /**
+     * Delivery date.
+     */
+    struct GNUNET_TIME_Timestamp delivery_date;
+
+    /**
+     * Delivery location.
+     */
+    json_t *delivery_location;
+
+    /**
+     * Array of products that are part of the purchase.
+     */
+    const json_t *products;
+
+    /**
+     * TODO: Maybe remove this and set it from settings where we serialize
+     *       the order to JSON?
+     *
+     * Information like name, website, email, etc. about the merchant.
+     */
+    json_t *merchant;
+
+    /**
+     * TODO: Maybe remove this and set it from settings where we serialize
+     *       the order to JSON?
+     *
+     * Merchant's public key
+     */
+    struct TALER_MerchantPublicKeyP merchant_pub;
+
+    /**
+    * Gross amount value of the contract. Used to
+    * compute @e max_stefan_fee.
+    */
+    struct TALER_Amount brutto;
+
+    /**
+     * Maximum fee as given by the client request.
+     */
+    struct TALER_Amount max_fee;
+
+    /**
+     * Specifies for how long the wallet should try to get an
+     * automatic refund for the purchase.
+     */
+    struct GNUNET_TIME_Relative auto_refund;
+
+    /**
+     * Nonce generated by the wallet and echoed by the merchant
+     * in this field when the proposal is generated.
+     */
+    const char *nonce;
+
+    /**
+     * Extra data that is only interpreted by the merchant frontend.
+     */
+    json_t *extra;
+
+  } parse_order;
 
   /**
-   * Claim token for the request.
+   * Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
    */
-  struct TALER_ClaimTokenP claim_token;
+  struct {
+    /**
+     * Merged array of products in the @e order.
+     */
+    json_t *products;
+  } merge_inventory;
 
   /**
-   * Length of the @e inventory_products array.
+   * Information set in the ORDER_PHASE_SET_EXCHANGES phase.
    */
-  unsigned int inventory_products_length;
+  struct {
+    /**
+    * Array of exchanges we find acceptable for this
+    * order.
+    */
+    json_t *exchanges;
+
+    /**
+     * Forced requests to /keys to update our exchange
+     * information.
+     */
+    struct RekeyExchange *pending_reload_head;
+
+    /**
+     * Forced requests to /keys to update our exchange
+     * information.
+     */
+    struct RekeyExchange *pending_reload_tail;
+
+    /**
+     * Did we previously force reloading of /keys from
+     * all exchanges? Set to 'true' to prevent us from
+     * doing it again (and again...).
+     */
+    bool forced_reload;
+
+    /**
+     * Set to true once we are sure that we have at
+     * least one good exchange.
+     */
+    bool exchange_good;
+
+    /**
+     * Maximum fee for @e order based on STEFAN curves.
+     * Used to set @e max_fee if not provided as part of
+     * @e order.
+     */
+    struct TALER_Amount max_stefan_fee;
+  } set_exchanges;
 
   /**
-   * Array of inventory products in the @e order.
+   * Information set in the ORDER_PHASE_SET_MAX_FEE phase.
    */
-  struct InventoryProduct *inventory_products;
+  struct {
+    /**
+     * Maximum fee
+     */
+    struct TALER_Amount max_fee;
+  } set_max_fee;
 
   /**
-   * array of UUIDs used to reserve products from @a inventory_products.
+   * Information set in the ORDER_PHASE_EXECUTE_ORDER phase.
    */
-  struct GNUNET_Uuid *uuids;
+  struct {
+    /**
+     * Which product (by offset) is out of stock, UINT_MAX if all were 
in-stock.
+     */
+    unsigned int out_of_stock_index;
+  } execute_order;
+
+  struct {
+    /**
+     * Contract terms to store in the database.
+     */
+    json_t *contract;
+  } serialize_order;
 
   /**
-   * Shared key to use with @e pos_algorithm.
+   * Connection of the request.
    */
-  const char *pos_key;
+  struct MHD_Connection *connection;
 
   /**
-   * Our order ID. Pointer into @e order.
+   * Kept in a DLL while suspended.
    */
-  const char *order_id;
+  struct OrderContext *next;
 
   /**
-   * which product (by offset) is out of stock, UINT_MAX if all were in-stock
+   * Kept in a DLL while suspended.
    */
-  unsigned int out_of_stock_index;
+  struct OrderContext *prev;
 
   /**
-   * Length of the @e uuids array.
+   * Handler context for the request.
    */
-  unsigned int uuids_length;
+  struct TMH_HandlerContext *hc;
 
   /**
    * #GNUNET_YES if suspended.
    */
   enum GNUNET_GenericReturnValue suspended;
 
-  /**
-   * Selected algorithm (by template) when we are to
-   * generate an OTP code for payment confirmation.
-   */
-  enum TALER_MerchantConfirmationAlgorithm pos_algorithm;
-
   /**
    * Current phase of setting up the order.
    */
   enum
   {
-    ORDER_PHASE_INIT,
+    ORDER_PHASE_PARSE_REQUEST,
+    ORDER_PHASE_PARSE_ORDER,
     ORDER_PHASE_MERGE_INVENTORY,
     ORDER_PHASE_ADD_PAYMENT_DETAILS,
-    ORDER_PHASE_PATCH_ORDER,
     ORDER_PHASE_SET_EXCHANGES,
     ORDER_PHASE_SET_MAX_FEE,
+    ORDER_PHASE_SERIALIZE_ORDER,
     ORDER_PHASE_CHECK_CONTRACT,
     ORDER_PHASE_EXECUTE_ORDER,
 
@@ -298,18 +471,6 @@ struct OrderContext
     ORDER_PHASE_FINISHED_MHD_NO
   } phase;
 
-  /**
-   * Set to true once we are sure that we have at
-   * least one good exchange.
-   */
-  bool exchange_good;
-
-  /**
-   * Did we previously force reloading of /keys from
-   * all exchanges? Set to 'true' to prevent us from
-   * doing it again (and again...).
-   */
-  bool forced_reload;
 
 };
 
@@ -413,27 +574,29 @@ clean_order (void *cls)
   struct OrderContext *oc = cls;
   struct RekeyExchange *rx;
 
-  while (NULL != (rx = oc->pending_reload_head))
+  while (NULL != (rx = oc->set_exchanges.pending_reload_head))
   {
-    GNUNET_CONTAINER_DLL_remove (oc->pending_reload_head,
-                                 oc->pending_reload_tail,
+    GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
+                                 oc->set_exchanges.pending_reload_tail,
                                  rx);
     TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
     GNUNET_free (rx->url);
     GNUNET_free (rx);
   }
-  if (NULL != oc->exchanges)
+  if (NULL != oc->set_exchanges.exchanges)
   {
-    json_decref (oc->exchanges);
-    oc->exchanges = NULL;
+    json_decref (oc->set_exchanges.exchanges);
+    oc->set_exchanges.exchanges = NULL;
   }
-  GNUNET_array_grow (oc->inventory_products,
-                     oc->inventory_products_length,
+  GNUNET_array_grow (oc->parse_request.inventory_products,
+                     oc->parse_request.inventory_products_length,
                      0);
-  GNUNET_array_grow (oc->uuids,
-                     oc->uuids_length,
+  GNUNET_array_grow (oc->parse_request.uuids,
+                     oc->parse_request.uuids_length,
                      0);
-  json_decref (oc->order);
+  json_decref (oc->parse_request.order);
+  /* TODO: Check that all other fields are cleaned up! */
+  json_decref (oc->serialize_order.contract);
   GNUNET_free (oc);
 }
 
@@ -461,14 +624,14 @@ execute_transaction (struct OrderContext *oc)
   /* Setup order */
   qs = TMH_db->insert_order (TMH_db->cls,
                              oc->hc->instance->settings.id,
-                             oc->order_id,
+                             oc->parse_order.order_id,
                              NULL /* session ID! FIXME: protocol v6! */,
-                             &oc->h_post_data,
-                             oc->pay_deadline,
-                             &oc->claim_token,
-                             oc->order,  /* called 'contract terms' at 
database. */
-                             oc->pos_key,
-                             oc->pos_algorithm);
+                             &oc->parse_request.h_post_data,
+                             oc->parse_order.pay_deadline,
+                             &oc->parse_request.claim_token,
+                             oc->serialize_order.contract,  /* called 
'contract terms' at database. */
+                             oc->parse_request.pos_key,
+                             oc->parse_request.pos_algorithm);
   if (qs <= 0)
   {
     /* qs == 0: probably instance does not exist (anymore) */
@@ -476,10 +639,10 @@ execute_transaction (struct OrderContext *oc)
     return qs;
   }
   /* Migrate locks from UUIDs to new order: first release old locks */
-  for (unsigned int i = 0; i<oc->uuids_length; i++)
+  for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
   {
     qs = TMH_db->unlock_inventory (TMH_db->cls,
-                                   &oc->uuids[i]);
+                                   &oc->parse_request.uuids[i]);
     if (qs < 0)
     {
       TMH_db->rollback (TMH_db->cls);
@@ -492,14 +655,14 @@ execute_transaction (struct OrderContext *oc)
      (note: this can basically ONLY fail on serializability OR
      because the UUID locks were insufficient for the desired
      quantities). */
-  for (unsigned int i = 0; i<oc->inventory_products_length; i++)
+  for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
   {
     qs = TMH_db->insert_order_lock (
       TMH_db->cls,
       oc->hc->instance->settings.id,
-      oc->order_id,
-      oc->inventory_products[i].product_id,
-      oc->inventory_products[i].quantity);
+      oc->parse_order.order_id,
+      oc->parse_request.inventory_products[i].product_id,
+      oc->parse_request.inventory_products[i].quantity);
     if (qs < 0)
     {
       TMH_db->rollback (TMH_db->cls);
@@ -509,17 +672,17 @@ execute_transaction (struct OrderContext *oc)
     {
       /* qs == 0: lock acquisition failed due to insufficient stocks */
       TMH_db->rollback (TMH_db->cls);
-      oc->out_of_stock_index = i; /* indicate which product is causing the 
issue */
+      oc->execute_order.out_of_stock_index = i; /* indicate which product is 
causing the issue */
       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     }
   }
-  oc->out_of_stock_index = UINT_MAX;
+  oc->execute_order.out_of_stock_index = UINT_MAX;
 
   /* Get the order serial and timestamp for the order we just created to
      update long-poll clients. */
   qs = TMH_db->lookup_order_summary (TMH_db->cls,
                                      oc->hc->instance->settings.id,
-                                     oc->order_id,
+                                     oc->parse_order.order_id,
                                      &timestamp,
                                      &order_serial);
   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -552,63 +715,27 @@ execute_order (struct OrderContext *oc)
 {
   const struct TALER_MERCHANTDB_InstanceSettings *settings =
     &oc->hc->instance->settings;
-  const char *summary;
-  const char *fulfillment_msg = NULL;
-  const json_t *products;
-  const json_t *merchant;
-  struct GNUNET_TIME_Timestamp timestamp;
-  struct GNUNET_TIME_Timestamp refund_deadline = { 0 };
-  struct GNUNET_TIME_Timestamp wire_transfer_deadline;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_string ("order_id",
-                             &oc->order_id),
-    TALER_JSON_spec_i18n_str ("summary",
-                              &summary),
-    /**
-     * The following entries we don't actually need,
-     * except to check that the order is well-formed */
-    GNUNET_JSON_spec_array_const ("products",
-                                  &products),
-    GNUNET_JSON_spec_object_const ("merchant",
-                                   &merchant),
-    GNUNET_JSON_spec_mark_optional (
-      TALER_JSON_spec_i18n_str ("fulfillment_message",
-                                &fulfillment_msg),
-      NULL),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &timestamp),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_timestamp ("refund_deadline",
-                                  &refund_deadline),
-      NULL),
-    GNUNET_JSON_spec_timestamp ("pay_deadline",
-                                &oc->pay_deadline),
-    GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
-                                &wire_transfer_deadline),
-    GNUNET_JSON_spec_end ()
-  };
   enum GNUNET_DB_QueryStatus qs;
 
   /* extract fields we need to sign separately */
-  {
-    enum GNUNET_GenericReturnValue res;
-
-    res = TALER_MHD_parse_json_data (oc->connection,
-                                     oc->order,
-                                     spec);
-    if (GNUNET_OK != res)
-    {
-      GNUNET_break_op (0);
-      finalize_order2 (oc,
-                       res);
-      return;
-    }
-  }
+  // {
+  //   enum GNUNET_GenericReturnValue res;
+
+  //   res = TALER_MHD_parse_json_data (oc->connection,
+  //                                    oc->order,
+  //                                    spec);
+  //   if (GNUNET_OK != res)
+  //   {
+  //     GNUNET_break_op (0);
+  //     finalize_order2 (oc,
+  //                      res);
+  //     return;
+  //   }
+  // }
 
   /* check product list in contract is well-formed */
-  if (! TMH_products_array_valid (products))
+  if (! TMH_products_array_valid (oc->merge_inventory.products))
   {
-    GNUNET_JSON_parse_free (spec);
     GNUNET_break_op (0);
     reply_with_error (oc,
                       MHD_HTTP_BAD_REQUEST,
@@ -626,7 +753,7 @@ execute_order (struct OrderContext *oc)
     TMH_db->preflight (TMH_db->cls);
     qs = TMH_db->lookup_order (TMH_db->cls,
                                oc->hc->instance->settings.id,
-                               oc->order_id,
+                               oc->parse_order.order_id,
                                &token,
                                &orig_post,
                                &contract_terms);
@@ -635,7 +762,6 @@ execute_order (struct OrderContext *oc)
     {
       GNUNET_break (0);
       TMH_db->rollback (TMH_db->cls);
-      GNUNET_JSON_parse_free (spec);
       reply_with_error (oc,
                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                         TALER_EC_GENERIC_DB_FETCH_FAILED,
@@ -650,7 +776,7 @@ execute_order (struct OrderContext *oc)
       /* Comparing the contract terms is sufficient because all the other
          params get added to it at some point. */
       if (0 == GNUNET_memcmp (&orig_post,
-                              &oc->h_post_data))
+                              &oc->parse_request.h_post_data))
       {
         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                     "Order creation idempotent\n");
@@ -658,7 +784,7 @@ execute_order (struct OrderContext *oc)
           oc->connection,
           MHD_HTTP_OK,
           GNUNET_JSON_pack_string ("order_id",
-                                   oc->order_id),
+                                   oc->parse_order.order_id),
           GNUNET_JSON_pack_allow_null (
             GNUNET_JSON_pack_data_varsize (
               "token",
@@ -666,7 +792,6 @@ execute_order (struct OrderContext *oc)
               ? NULL
               : &token,
               sizeof (token))));
-        GNUNET_JSON_parse_free (spec);
         finalize_order (oc,
                         ret);
         return;
@@ -677,14 +802,13 @@ execute_order (struct OrderContext *oc)
         oc,
         MHD_HTTP_CONFLICT,
         TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
-        oc->order_id);
-      GNUNET_JSON_parse_free (spec);
+        oc->parse_order.order_id);
       return;
     }
   }
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Executing database transaction to create order '%s' for 
instance '%s'\n",
-              oc->order_id,
+              oc->parse_order.order_id,
               settings->id);
   for (unsigned int i = 0; i<MAX_RETRIES; i++)
   {
@@ -695,7 +819,6 @@ execute_order (struct OrderContext *oc)
   }
   if (0 >= qs)
   {
-    GNUNET_JSON_parse_free (spec);
     /* Special report if retries insufficient */
     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
@@ -714,7 +837,7 @@ execute_order (struct OrderContext *oc)
         oc,
         MHD_HTTP_CONFLICT,
         TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
-        oc->order_id);
+        oc->parse_order.order_id);
       return;
     }
     /* Other hard transaction error (disk full, etc.) */
@@ -728,7 +851,7 @@ execute_order (struct OrderContext *oc)
   }
 
   /* DB transaction succeeded, check for out-of-stock */
-  if (oc->out_of_stock_index < UINT_MAX)
+  if (oc->execute_order.out_of_stock_index < UINT_MAX)
   {
     /* We had a product that has insufficient quantities,
        generate the details for the response. */
@@ -741,9 +864,8 @@ execute_order (struct OrderContext *oc)
     qs = TMH_db->lookup_product (
       TMH_db->cls,
       oc->hc->instance->settings.id,
-      oc->inventory_products[oc->out_of_stock_index].product_id,
+      
oc->parse_request.inventory_products[oc->execute_order.out_of_stock_index].product_id,
       &pd);
-    GNUNET_JSON_parse_free (spec);
     switch (qs)
     {
     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
@@ -754,10 +876,10 @@ execute_order (struct OrderContext *oc)
         MHD_HTTP_GONE,
         GNUNET_JSON_pack_string (
           "product_id",
-          oc->inventory_products[oc->out_of_stock_index].product_id),
+          
oc->parse_request.inventory_products[oc->execute_order.out_of_stock_index].product_id),
         GNUNET_JSON_pack_uint64 (
           "requested_quantity",
-          oc->inventory_products[oc->out_of_stock_index].quantity),
+          
oc->parse_request.inventory_products[oc->execute_order.out_of_stock_index].quantity),
         GNUNET_JSON_pack_uint64 (
           "available_quantity",
           pd.total_stock - pd.total_sold - pd.total_lost),
@@ -778,11 +900,11 @@ execute_order (struct OrderContext *oc)
                         MHD_HTTP_GONE,
                         GNUNET_JSON_pack_string (
                           "product_id",
-                          oc->inventory_products[oc->out_of_stock_index].
+                          
oc->parse_request.inventory_products[oc->execute_order.out_of_stock_index].
                           product_id),
                         GNUNET_JSON_pack_uint64 (
                           "requested_quantity",
-                          oc->inventory_products[oc->out_of_stock_index].
+                          
oc->parse_request.inventory_products[oc->execute_order.out_of_stock_index].
                           quantity),
                         GNUNET_JSON_pack_uint64 (
                           "available_quantity",
@@ -820,15 +942,14 @@ execute_order (struct OrderContext *oc)
       oc->connection,
       MHD_HTTP_OK,
       GNUNET_JSON_pack_string ("order_id",
-                               oc->order_id),
+                               oc->parse_order.order_id),
       GNUNET_JSON_pack_allow_null (
         GNUNET_JSON_pack_data_varsize (
           "token",
-          GNUNET_is_zero (&oc->claim_token)
+          GNUNET_is_zero (&oc->parse_request.claim_token)
           ? NULL
-          : &oc->claim_token,
-          sizeof (oc->claim_token))));
-    GNUNET_JSON_parse_free (spec);
+          : &oc->parse_request.claim_token,
+          sizeof (oc->parse_request.claim_token))));
     finalize_order (oc,
                     ret);
   }
@@ -836,7 +957,8 @@ execute_order (struct OrderContext *oc)
 
 
 /**
- * Check that the contract is now well-formed.
+ * Check that the contract is now well-formed. Upon success, continue
+ * processing with execute_order().
  *
  * @param[in,out] oc order context
  */
@@ -845,7 +967,7 @@ check_contract (struct OrderContext *oc)
 {
   struct TALER_PrivateContractHashP h_control;
 
-  switch (TALER_JSON_contract_hash (oc->order,
+  switch (TALER_JSON_contract_hash (oc->serialize_order.contract,
                                     &h_control))
   {
   case GNUNET_SYSERR:
@@ -854,7 +976,7 @@ check_contract (struct OrderContext *oc)
       oc,
       MHD_HTTP_INTERNAL_SERVER_ERROR,
       TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
-      "could not compute hash of patched order");
+      "could not compute hash of serialized order");
     return;
   case GNUNET_NO:
     GNUNET_break_op (0);
@@ -886,33 +1008,33 @@ update_stefan (struct OrderContext *oc,
 
   if (GNUNET_SYSERR !=
       TALER_EXCHANGE_keys_stefan_b2n (keys,
-                                      &oc->brutto,
+                                      &oc->parse_order.brutto,
                                       &net))
   {
     struct TALER_Amount fee;
 
     TALER_EXCHANGE_keys_stefan_round (keys,
                                       &net);
-    if (-1 == TALER_amount_cmp (&oc->brutto,
+    if (-1 == TALER_amount_cmp (&oc->parse_order.brutto,
                                 &net))
     {
       /* brutto < netto! */
       /* => after rounding, there is no real difference */
-      net = oc->brutto;
+      net = oc->parse_order.brutto;
     }
     GNUNET_assert (0 <=
                    TALER_amount_subtract (&fee,
-                                          &oc->brutto,
+                                          &oc->parse_order.brutto,
                                           &net));
     if ( (GNUNET_OK !=
-          TALER_amount_is_valid (&oc->max_stefan_fee)) ||
-         (-1 == TALER_amount_cmp (&oc->max_stefan_fee,
+          TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) ||
+         (-1 == TALER_amount_cmp (&oc->set_exchanges.max_stefan_fee,
                                   &fee)) )
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   "Updated STEFAN-based fee to %s\n",
                   TALER_amount2s (&fee));
-      oc->max_stefan_fee = fee;
+      oc->set_exchanges.max_stefan_fee = fee;
     }
   }
 }
@@ -937,7 +1059,7 @@ get_acceptable (void *cls,
   enum GNUNET_GenericReturnValue res;
 
   res = TMH_exchange_check_debit (exchange,
-                                  oc->wm);
+                                  oc->add_payment_details.wm);
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Exchange %s evaluated at %d\n",
               url,
@@ -946,16 +1068,16 @@ get_acceptable (void *cls,
   {
   case GNUNET_OK:
     priority = 1024;   /* high */
-    oc->exchange_good = true;
+    oc->set_exchanges.exchange_good = true;
     break;
   case GNUNET_NO:
-    if (oc->forced_reload)
+    if (oc->set_exchanges.forced_reload)
       priority = 0;   /* fresh negative response */
     else
       priority = 512; /* stale negative response */
     break;
   case GNUNET_SYSERR:
-    if (oc->forced_reload)
+    if (oc->set_exchanges.forced_reload)
       priority = 256;   /* fresh, no accounts yet */
     else
       priority = 768;  /* stale, no accounts yet */
@@ -970,7 +1092,7 @@ get_acceptable (void *cls,
                                 TMH_EXCHANGES_get_master_pub (exchange)));
   GNUNET_assert (NULL != j_exchange);
   GNUNET_assert (0 ==
-                 json_array_append_new (oc->exchanges,
+                 json_array_append_new (oc->set_exchanges.exchanges,
                                         j_exchange));
 }
 
@@ -986,7 +1108,7 @@ resume_with_keys (struct OrderContext *oc)
 {
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Resuming order processing after /keys downloads (now have %u 
accounts)\n",
-              (unsigned int) json_array_size (oc->exchanges));
+              (unsigned int) json_array_size (oc->set_exchanges.exchanges));
   GNUNET_assert (GNUNET_YES == oc->suspended);
   GNUNET_CONTAINER_DLL_remove (oc_head,
                                oc_tail,
@@ -1017,8 +1139,8 @@ keys_cb (
     &oc->hc->instance->settings;
 
   rx->fo = NULL;
-  GNUNET_CONTAINER_DLL_remove (oc->pending_reload_head,
-                               oc->pending_reload_tail,
+  GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
+                               oc->set_exchanges.pending_reload_tail,
                                rx);
   if (NULL == keys)
   {
@@ -1033,7 +1155,7 @@ keys_cb (
                 rx->url);
     if ( (settings->use_stefan) &&
          (GNUNET_OK !=
-          TALER_amount_is_valid (&oc->max_fee)) )
+          TALER_amount_is_valid (&oc->parse_order.max_fee)) )
       update_stefan (oc,
                      keys);
     get_acceptable (oc,
@@ -1042,7 +1164,7 @@ keys_cb (
   }
   GNUNET_free (rx->url);
   GNUNET_free (rx);
-  if (NULL != oc->pending_reload_head)
+  if (NULL != oc->set_exchanges.pending_reload_head)
     return;
   resume_with_keys (oc);
 }
@@ -1068,23 +1190,110 @@ get_exchange_keys (void *cls,
   rx = GNUNET_new (struct RekeyExchange);
   rx->oc = oc;
   rx->url = GNUNET_strdup (url);
-  GNUNET_CONTAINER_DLL_insert (oc->pending_reload_head,
-                               oc->pending_reload_tail,
+  GNUNET_CONTAINER_DLL_insert (oc->set_exchanges.pending_reload_head,
+                               oc->set_exchanges.pending_reload_tail,
                                rx);
-  if (oc->forced_reload)
+  if (oc->set_exchanges.forced_reload)
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Forcing download of %skeys\n",
                 url);
   rx->fo = TMH_EXCHANGES_keys4exchange (url,
-                                        oc->forced_reload,
+                                        oc->set_exchanges.forced_reload,
                                         &keys_cb,
                                         rx);
 }
 
 
+/**
+ * Serialize order into @a oc->serialize_order.contract,
+ * ready to be stored in the database. Upon success, continue
+ * processing with check_contract().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+serialize_order (struct OrderContext *oc)
+{
+  oc->serialize_order.contract = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("summary",
+                              oc->parse_order.summary),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_object_steal ("summary_i18n",
+                                    oc->parse_order.summary_i18n)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("public_reorder_url",
+                                oc->parse_order.public_reorder_url)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("fulfillment_message",
+                                oc->parse_order.fulfillment_message)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
+                                    oc->parse_order.fulfillment_message_i18n)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("fulfillment_url",
+                                oc->parse_order.fulfillment_url)),
+    GNUNET_JSON_pack_array_steal ("products",
+                                   oc->merge_inventory.products),
+    GNUNET_JSON_pack_data_auto ("h_wire",
+                                &oc->add_payment_details.wm->h_wire),
+    GNUNET_JSON_pack_string ("wire_method",
+                              oc->add_payment_details.wm->wire_method),
+    GNUNET_JSON_pack_string ("order_id",
+                              oc->parse_order.order_id),
+    GNUNET_JSON_pack_timestamp ("timestamp",
+                                oc->parse_order.timestamp),
+    GNUNET_JSON_pack_timestamp ("pay_deadline",
+                                oc->parse_order.pay_deadline),
+    GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
+                                oc->parse_order.wire_deadline),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_timestamp ("delivery_date",
+                                  oc->parse_order.delivery_date)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_object_steal ("delivery_location",
+                                      oc->parse_order.delivery_location)),
+    GNUNET_JSON_pack_string ("merchant_base_url",
+                              oc->parse_order.merchant_base_url),
+    GNUNET_JSON_pack_object_steal ("merchant",
+                                   oc->parse_order.merchant),
+    GNUNET_JSON_pack_data_auto ("merchant_pub",
+                                &oc->hc->instance->merchant_pub),
+    GNUNET_JSON_pack_array_steal ("exchanges",
+                                  oc->set_exchanges.exchanges),
+    TALER_JSON_pack_amount ("max_fee",
+                            &oc->set_max_fee.max_fee),
+    TALER_JSON_pack_amount ("amount",
+                            &oc->parse_order.brutto),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_object_steal ("extra",
+                                    oc->parse_order.extra))
+  );
+
+  /* Pack does not work here, because it doesn't set zero-values for 
timestamps */
+  GNUNET_assert (0 ==
+                 json_object_set_new (oc->serialize_order.contract,
+                                      "refund_deadline",
+                                      GNUNET_JSON_from_timestamp (
+                                        oc->parse_order.refund_deadline)));
+
+  /* Pack does not work here, because it sets zero-values for relative times */
+  /* auto_refund should only be set if it is not 0 */
+  if (!GNUNET_TIME_relative_is_zero(oc->parse_order.auto_refund)) {
+    GNUNET_assert (0 ==
+                   json_object_set_new (oc->serialize_order.contract,
+                                      "auto_refund",
+                                      GNUNET_JSON_from_time_rel (
+                                        oc->parse_order.auto_refund)));
+  }
+
+  oc->phase++;
+}
+
+
 /**
  * Set max_fee in @a oc based on STEFAN value if
- * not yet present.
+ * not yet present. Upon success, continue
+ * processing with serialize_order().
  *
  * @param[in,out] oc order context
  */
@@ -1095,30 +1304,27 @@ set_max_fee (struct OrderContext *oc)
     &oc->hc->instance->settings;
 
   if (GNUNET_OK !=
-      TALER_amount_is_valid (&oc->max_fee))
+      TALER_amount_is_valid (&oc->parse_order.max_fee))
   {
     struct TALER_Amount stefan;
 
     if ( (settings->use_stefan) &&
          (GNUNET_OK ==
-          TALER_amount_is_valid (&oc->max_stefan_fee)) )
-      stefan = oc->max_stefan_fee;
+          TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) )
+      stefan = oc->set_exchanges.max_stefan_fee;
     else
       GNUNET_assert (GNUNET_OK ==
-                     TALER_amount_set_zero (oc->brutto.currency,
+                     TALER_amount_set_zero (oc->parse_order.brutto.currency,
                                             &stefan));
-    GNUNET_assert (0 ==
-                   json_object_set_new (
-                     oc->order,
-                     "max_fee",
-                     TALER_JSON_from_amount (&stefan)));
+    oc->set_max_fee.max_fee = stefan;
   }
   oc->phase++;
 }
 
 
 /**
- * Set list of acceptable exchanges in @a oc.
+ * Set list of acceptable exchanges in @a oc. Upon success, continue
+ * processing with set_max_fee().
  *
  * @param[in,out] oc order context
  * @return true to suspend execution
@@ -1129,28 +1335,28 @@ set_exchanges (struct OrderContext *oc)
   /* Note: re-building 'oc->exchanges' every time here might be a tad
      expensive; could likely consider caching the result if it starts to
      matter. */
-  if (NULL == oc->exchanges)
+  if (NULL == oc->set_exchanges.exchanges)
   {
-    oc->exchanges = json_array ();
+    oc->set_exchanges.exchanges = json_array ();
     TMH_exchange_get_trusted (&get_exchange_keys,
                               oc);
   }
-  else if (! oc->exchange_good)
+  else if (! oc->set_exchanges.exchange_good)
   {
-    if (! oc->forced_reload)
+    if (! oc->set_exchanges.forced_reload)
     {
-      oc->forced_reload = true;
+      oc->set_exchanges.forced_reload = true;
       GNUNET_assert (0 ==
-                     json_array_clear (oc->exchanges));
+                     json_array_clear (oc->set_exchanges.exchanges));
       TMH_exchange_get_trusted (&get_exchange_keys,
                                 oc);
     }
   }
-  if (NULL != oc->pending_reload_head)
+  if (NULL != oc->set_exchanges.pending_reload_head)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Still trying to (re)load %skeys\n",
-                oc->pending_reload_head->url);
+                oc->set_exchanges.pending_reload_head->url);
     MHD_suspend_connection (oc->connection);
     oc->suspended = GNUNET_YES;
     GNUNET_CONTAINER_DLL_insert (oc_head,
@@ -1158,7 +1364,7 @@ set_exchanges (struct OrderContext *oc)
                                  oc);
     return true; /* reloads pending */
   }
-  if (0 == json_array_size (oc->exchanges))
+  if (0 == json_array_size (oc->set_exchanges.exchanges))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Cannot create order: lacking trusted exchanges\n");
@@ -1166,110 +1372,122 @@ set_exchanges (struct OrderContext *oc)
       oc,
       MHD_HTTP_CONFLICT,
       TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
-      oc->wm->wire_method);
+      oc->add_payment_details.wm->wire_method);
     return false;
   }
-  if (! oc->exchange_good)
+  if (! oc->set_exchanges.exchange_good)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Creating order, but possibly without usable trusted 
exchanges\n");
   }
-  /* 'set' is correct here: reference in oc->exchanges released in 
cleanup_order() */
-  GNUNET_assert (0 ==
-                 json_object_set (oc->order,
-                                  "exchanges",
-                                  oc->exchanges));
   oc->phase++;
   return false;
 }
 
 
 /**
- * Add missing fields to the order.  Upon success, continue
- * processing with execute_order().
+ * Add missing fields to the order. Upon success, continue
+ * processing with merge_inventory().
  *
  * @param[in,out] oc order context
  */
 static void
-patch_order (struct OrderContext *oc)
+parse_order (struct OrderContext *oc)
 {
   const struct TALER_MERCHANTDB_InstanceSettings *settings =
     &oc->hc->instance->settings;
-  const char *order_id = NULL;
-  const char *fulfillment_url = NULL;
-  const char *merchant_base_url = NULL;
+
+  oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+  oc->parse_order.wire_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+
   const json_t *jmerchant = NULL;
-  const json_t *delivery_location = NULL;
-  struct GNUNET_TIME_Timestamp timestamp
-    = GNUNET_TIME_UNIT_ZERO_TS;
-  struct GNUNET_TIME_Timestamp delivery_date
-    = GNUNET_TIME_UNIT_ZERO_TS;
-  struct GNUNET_TIME_Timestamp refund_deadline
-    = GNUNET_TIME_UNIT_FOREVER_TS;
-  struct GNUNET_TIME_Timestamp wire_deadline
-    = GNUNET_TIME_UNIT_FOREVER_TS;
   /* auto_refund only needs to be type-checked,
    * mostly because in GNUnet relative times can't
    * be negative.  */
-  struct GNUNET_TIME_Relative auto_refund;
   bool no_fee;
   struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("amount",
+                                &oc->parse_order.brutto),
+    GNUNET_JSON_spec_string("summary",
+                             &oc->parse_order.summary),
     GNUNET_JSON_spec_mark_optional (
-      TALER_JSON_spec_web_url ("merchant_base_url",
-                               &merchant_base_url),
-      NULL),
+      GNUNET_JSON_spec_array_const ("products",
+                                    &oc->parse_order.products),
+        NULL),
     GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_object_const ("merchant",
-                                     &jmerchant),
+      GNUNET_JSON_spec_json ("summary_i18n",
+                            &oc->parse_order.summary_i18n),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("order_id",
-                               &order_id),
+                               &oc->parse_order.order_id),
+      NULL),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string ("public_reorder_url",
+                               &oc->parse_order.public_reorder_url),
+      NULL),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string("fulfillment_message",
+                              &oc->parse_order.fulfillment_message),
+      NULL),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_json ("fulfillment_message_i18n",
+                            &oc->parse_order.fulfillment_message_i18n),
       NULL),
-    TALER_JSON_spec_amount_any ("amount",
-                                &oc->brutto),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("fulfillment_url",
-                               &fulfillment_url),
+                             &oc->parse_order.fulfillment_url),
+      NULL),
+    GNUNET_JSON_spec_mark_optional (
+      TALER_JSON_spec_web_url ("merchant_base_url",
+                               &oc->parse_order.merchant_base_url),
+      NULL),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_object_const ("merchant",
+                                     &jmerchant),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("timestamp",
-                                  &timestamp),
+                                     &oc->parse_order.timestamp),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("refund_deadline",
-                                  &refund_deadline),
+                                     &oc->parse_order.refund_deadline),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("pay_deadline",
-                                  &oc->pay_deadline),
+                                     &oc->parse_order.pay_deadline),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
-                                  &wire_deadline),
+                                     &oc->parse_order.wire_deadline),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       TALER_JSON_spec_amount_any ("max_fee",
-                                  &oc->max_fee),
+                              &oc->parse_order.max_fee),
       &no_fee),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_json ("delivery_location",
+                               &oc->parse_order.delivery_location),
+      NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("delivery_date",
-                                  &delivery_date),
+                                     &oc->parse_order.delivery_date),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_relative_time ("auto_refund",
-                                      &auto_refund),
+                                        &oc->parse_order.auto_refund),
       NULL),
     GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_object_const ("delivery_location",
-                                     &delivery_location),
+      GNUNET_JSON_spec_json ("extra",
+                            &oc->parse_order.extra),
       NULL),
     GNUNET_JSON_spec_end ()
   };
   enum GNUNET_GenericReturnValue ret;
 
   ret = TALER_MHD_parse_json_data (oc->connection,
-                                   oc->order,
+                                   oc->parse_request.order,
                                    spec);
   if (GNUNET_OK != ret)
   {
@@ -1278,7 +1496,7 @@ patch_order (struct OrderContext *oc)
                      ret);
     return;
   }
-  if (! TMH_test_exchange_configured_for_currency (oc->brutto.currency))
+  if (! TMH_test_exchange_configured_for_currency 
(oc->parse_order.brutto.currency))
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
@@ -1290,8 +1508,8 @@ patch_order (struct OrderContext *oc)
   }
   if ( (! no_fee) &&
        (GNUNET_OK !=
-        TALER_amount_cmp_currency (&oc->brutto,
-                                   &oc->max_fee)) )
+        TALER_amount_cmp_currency (&oc->parse_order.brutto,
+                                   &oc->parse_order.max_fee)) )
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
@@ -1303,7 +1521,7 @@ patch_order (struct OrderContext *oc)
   }
 
   /* Add order_id if it doesn't exist. */
-  if (NULL == order_id)
+  if (NULL == oc->parse_order.order_id)
   {
     char buf[256];
     time_t timer;
@@ -1311,7 +1529,6 @@ patch_order (struct OrderContext *oc)
     size_t off;
     uint64_t rand;
     char *last;
-    json_t *jbuf;
 
     time (&timer);
     tm_info = localtime (&timer);
@@ -1340,25 +1557,21 @@ patch_order (struct OrderContext *oc)
                                           sizeof (buf) - off);
     GNUNET_assert (NULL != last);
     *last = '\0';
-    jbuf = json_string (buf);
-    GNUNET_assert (NULL != jbuf);
+
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Assigning order ID `%s' server-side\n",
                 buf);
-    GNUNET_break (0 ==
-                  json_object_set_new (oc->order,
-                                       "order_id",
-                                       jbuf));
-    order_id = json_string_value (jbuf);
-    GNUNET_assert (NULL != order_id);
+
+    oc->parse_order.order_id = GNUNET_strdup (buf);
+    GNUNET_assert (NULL != oc->parse_order.order_id);
   }
 
   /* Patch fulfillment URL with order_id (implements #6467). */
-  if (NULL != fulfillment_url)
+  if (NULL != oc->parse_order.fulfillment_url)
   {
     const char *pos;
 
-    pos = strstr (fulfillment_url,
+    pos = strstr (oc->parse_order.fulfillment_url,
                   "${ORDER_ID}");
     if (NULL != pos)
     {
@@ -1380,17 +1593,15 @@ patch_order (struct OrderContext *oc)
       GNUNET_asprintf (&nurl,
                        "%.*s%s%s",
                        /* first output URL until ${ORDER_ID} */
-                       (int) (pos - fulfillment_url),
-                       fulfillment_url,
+                       (int) (pos - oc->parse_order.fulfillment_url),
+                       oc->parse_order.fulfillment_url,
                        /* replace ${ORDER_ID} with the right order_id */
-                       order_id,
+                       oc->parse_order.order_id,
                        /* append rest of original URL */
                        pos + strlen ("${ORDER_ID}"));
-      /* replace in JSON of the order */
-      GNUNET_break (0 ==
-                    json_object_set_new (oc->order,
-                                         "fulfillment_url",
-                                         json_string (nurl)));
+
+      oc->parse_order.fulfillment_url = GNUNET_strdup (nurl);
+
       GNUNET_free (nurl);
     }
   }
@@ -1401,35 +1612,28 @@ patch_order (struct OrderContext *oc)
     struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
 
     /* Add timestamp if it doesn't exist (or is zero) */
-    if (GNUNET_TIME_absolute_is_zero (timestamp.abs_time))
+    if (GNUNET_TIME_absolute_is_zero (oc->parse_order.timestamp.abs_time))
     {
-      GNUNET_assert (0 ==
-                     json_object_set_new (oc->order,
-                                          "timestamp",
-                                          GNUNET_JSON_from_timestamp (now)));
+      oc->parse_order.timestamp = now;
     }
 
     /* If no refund_deadline given, set one based on refund_delay.  */
-    if (GNUNET_TIME_absolute_is_never (refund_deadline.abs_time))
+    if (GNUNET_TIME_absolute_is_never 
(oc->parse_order.refund_deadline.abs_time))
     {
-      if (GNUNET_TIME_relative_is_zero (oc->refund_delay))
+      if (GNUNET_TIME_relative_is_zero (oc->parse_request.refund_delay))
       {
         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                     "Refund delay is zero, no refunds are possible for this 
order\n");
-        refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
+        oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
       }
       else
       {
-        refund_deadline = GNUNET_TIME_relative_to_timestamp (oc->refund_delay);
+        oc->parse_order.refund_deadline = GNUNET_TIME_relative_to_timestamp 
(oc->parse_request.refund_delay);
       }
-      GNUNET_assert (0 ==
-                     json_object_set_new (oc->order,
-                                          "refund_deadline",
-                                          GNUNET_JSON_from_timestamp (
-                                            refund_deadline)));
     }
-    if ( (! GNUNET_TIME_absolute_is_zero (delivery_date.abs_time)) &&
-         (GNUNET_TIME_absolute_is_past (delivery_date.abs_time)) )
+
+    if ( (! GNUNET_TIME_absolute_is_zero 
(oc->parse_order.delivery_date.abs_time)) &&
+         (GNUNET_TIME_absolute_is_past 
(oc->parse_order.delivery_date.abs_time)) )
     {
       GNUNET_break_op (0);
       reply_with_error (
@@ -1441,17 +1645,12 @@ patch_order (struct OrderContext *oc)
     }
   }
 
-  if (GNUNET_TIME_absolute_is_zero (oc->pay_deadline.abs_time))
+  if (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time))
   {
-    oc->pay_deadline = GNUNET_TIME_relative_to_timestamp (
+    oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp (
       settings->default_pay_delay);
-    GNUNET_assert (0 ==
-                   json_object_set_new (oc->order,
-                                        "pay_deadline",
-                                        GNUNET_JSON_from_timestamp (
-                                          oc->pay_deadline)));
   }
-  else if (GNUNET_TIME_absolute_is_past (oc->pay_deadline.abs_time))
+  else if (GNUNET_TIME_absolute_is_past 
(oc->parse_order.pay_deadline.abs_time))
   {
     GNUNET_break_op (0);
     reply_with_error (
@@ -1461,8 +1660,9 @@ patch_order (struct OrderContext *oc)
       NULL);
     return;
   }
-  if ( (! GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time)) &&
-       (GNUNET_TIME_absolute_is_past (refund_deadline.abs_time)) )
+
+  if ( (! GNUNET_TIME_absolute_is_zero 
(oc->parse_order.refund_deadline.abs_time)) &&
+       (GNUNET_TIME_absolute_is_past 
(oc->parse_order.refund_deadline.abs_time)) )
   {
     GNUNET_break_op (0);
     reply_with_error (
@@ -1473,16 +1673,16 @@ patch_order (struct OrderContext *oc)
     return;
   }
 
-  if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
+  if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
   {
     struct GNUNET_TIME_Timestamp t;
 
     t = GNUNET_TIME_relative_to_timestamp (
       GNUNET_TIME_relative_max (settings->default_wire_transfer_delay,
-                                oc->refund_delay));
-    wire_deadline = GNUNET_TIME_timestamp_max (refund_deadline,
+                                oc->parse_request.refund_delay));
+    oc->parse_order.wire_deadline = GNUNET_TIME_timestamp_max 
(oc->parse_order.refund_deadline,
                                                t);
-    if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
+    if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
     {
       GNUNET_break_op (0);
       reply_with_error (
@@ -1492,15 +1692,10 @@ patch_order (struct OrderContext *oc)
         "order:wire_transfer_deadline");
       return;
     }
-    GNUNET_assert (0 ==
-                   json_object_set_new (oc->order,
-                                        "wire_transfer_deadline",
-                                        GNUNET_JSON_from_timestamp (
-                                          wire_deadline)));
   }
-  if (GNUNET_TIME_timestamp_cmp (wire_deadline,
+  if (GNUNET_TIME_timestamp_cmp (oc->parse_order.wire_deadline,
                                  <,
-                                 refund_deadline))
+                                 oc->parse_order.refund_deadline))
   {
     GNUNET_break_op (0);
     reply_with_error (
@@ -1511,7 +1706,7 @@ patch_order (struct OrderContext *oc)
     return;
   }
 
-  if (NULL == merchant_base_url)
+  if (NULL == oc->parse_order.merchant_base_url)
   {
     char *url;
 
@@ -1527,14 +1722,11 @@ patch_order (struct OrderContext *oc)
         "order:merchant_base_url");
       return;
     }
-    GNUNET_assert (0 ==
-                   json_object_set_new (oc->order,
-                                        "merchant_base_url",
-                                        json_string (url)));
+    oc->parse_order.merchant_base_url = GNUNET_strdup (url);
     GNUNET_free (url);
   }
-  else if (('\0' == *merchant_base_url) ||
-           ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
+  else if (('\0' == *oc->parse_order.merchant_base_url) ||
+           ('/' != oc->parse_order.merchant_base_url[strlen 
(oc->parse_order.merchant_base_url) - 1]))
   {
     GNUNET_break_op (0);
     reply_with_error (
@@ -1545,6 +1737,20 @@ patch_order (struct OrderContext *oc)
     return;
   }
 
+  if (NULL == oc->parse_order.products)
+  {
+    oc->parse_order.products = json_array ();
+  }
+  else if (! TMH_products_array_valid (oc->parse_order.products))
+  {
+    reply_with_error (
+      oc,
+      MHD_HTTP_BAD_REQUEST,
+      TALER_EC_GENERIC_PARAMETER_MALFORMED,
+      "order.products");
+    return;
+  }
+
   /* Merchant information must not already be present */
   if (NULL != jmerchant)
   {
@@ -1558,9 +1764,7 @@ patch_order (struct OrderContext *oc)
   }
 
   {
-    json_t *jm;
-
-    jm = GNUNET_JSON_PACK (
+    oc->parse_order.merchant = GNUNET_JSON_PACK (
       GNUNET_JSON_pack_string ("name",
                                settings->name),
       GNUNET_JSON_pack_allow_null (
@@ -1572,7 +1776,7 @@ patch_order (struct OrderContext *oc)
       GNUNET_JSON_pack_allow_null (
         GNUNET_JSON_pack_string ("logo",
                                  settings->logo)));
-    GNUNET_assert (NULL != jm);
+    GNUNET_assert (NULL != oc->parse_order.merchant);
     {
       json_t *loca;
 
@@ -1582,7 +1786,7 @@ patch_order (struct OrderContext *oc)
       {
         loca = json_deep_copy (loca);
         GNUNET_assert (0 ==
-                       json_object_set_new (jm,
+                       json_object_set_new (oc->parse_order.merchant,
                                             "address",
                                             loca));
       }
@@ -1596,36 +1800,29 @@ patch_order (struct OrderContext *oc)
       {
         locj = json_deep_copy (locj);
         GNUNET_assert (0 ==
-                       json_object_set_new (jm,
+                       json_object_set_new (oc->parse_order.merchant,
                                             "jurisdiction",
                                             locj));
       }
     }
-    GNUNET_assert (0 ==
-                   json_object_set_new (oc->order,
-                                        "merchant",
-                                        jm));
   }
 
-  GNUNET_assert (0 ==
-                 json_object_set_new (oc->order,
-                                      "merchant_pub",
-                                      GNUNET_JSON_from_data_auto (
-                                        &oc->hc->instance->merchant_pub)));
-
-  if (GNUNET_OK !=
-      TALER_JSON_contract_seed_forgettable (oc->order))
-  {
-    reply_with_error (
-      oc,
-      MHD_HTTP_BAD_REQUEST,
-      TALER_EC_GENERIC_JSON_INVALID,
-      "could not compute hash of order due to bogus forgettable fields");
-    return;
-  }
-
-  if ( (NULL != delivery_location) &&
-       (! TMH_location_object_valid (delivery_location)) )
+  oc->parse_order.merchant_pub = oc->hc->instance->merchant_pub;
+
+  /* TODO: Not sure yet how to properly handle this in the refactored code */
+  // if (GNUNET_OK !=
+  //     TALER_JSON_contract_seed_forgettable (oc->order))
+  // {
+  //   reply_with_error (
+  //     oc,
+  //     MHD_HTTP_BAD_REQUEST,
+  //     TALER_EC_GENERIC_JSON_INVALID,
+  //     "could not compute hash of order due to bogus forgettable fields");
+  //   return;
+  // }
+
+  if ( (NULL != oc->parse_order.delivery_location) &&
+       (! TMH_location_object_valid (oc->parse_order.delivery_location)) )
   {
     GNUNET_break_op (0);
     reply_with_error (oc,
@@ -1634,6 +1831,7 @@ patch_order (struct OrderContext *oc)
                       "delivery_location");
     return;
   }
+
   oc->phase++;
 }
 
@@ -1641,7 +1839,7 @@ patch_order (struct OrderContext *oc)
 /**
  * Process the @a payment_target and add the details of how the
  * order could be paid to @a order. On success, continue
- * processing with patch_order().
+ * processing with set_exchanges().
  *
  * @param[in,out] oc order context
  */
@@ -1654,8 +1852,8 @@ add_payment_details (struct OrderContext *oc)
   /* Locate wire method that has a matching payment target */
   while ( (NULL != wm) &&
           ( (! wm->active) ||
-            ( (NULL != oc->payment_target) &&
-              (0 != strcasecmp (oc->payment_target,
+            ( (NULL != oc->parse_request.payment_target) &&
+              (0 != strcasecmp (oc->parse_request.payment_target,
                                 wm->wire_method) ) ) ) )
     wm = wm->next;
   if (NULL == wm)
@@ -1666,19 +1864,10 @@ add_payment_details (struct OrderContext *oc)
     reply_with_error (oc,
                       MHD_HTTP_NOT_FOUND,
                       
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
-                      oc->payment_target);
+                      oc->parse_request.payment_target);
     return;
   }
-  oc->wm = wm;
-  GNUNET_assert (0 ==
-                 json_object_set_new (oc->order,
-                                      "h_wire",
-                                      GNUNET_JSON_from_data_auto (
-                                        &wm->h_wire)));
-  GNUNET_assert (0 ==
-                 json_object_set_new (oc->order,
-                                      "wire_method",
-                                      json_string (wm->wire_method)));
+  oc->add_payment_details.wm = wm;
   oc->phase++;
 }
 
@@ -1694,42 +1883,21 @@ static void
 merge_inventory (struct OrderContext *oc)
 {
   /**
-   * inventory_products => instructions to add products to contract terms
-   * order.products => contains products that are not from the backend-managed 
inventory.
+   * parse_request.inventory_products => instructions to add products to 
contract terms
+   * parse_order.products => contains products that are not from the 
backend-managed inventory.
    */
-  {
-    json_t *jprod = json_object_get (oc->order,
-                                     "products");
-    if (NULL == jprod)
-    {
-      GNUNET_assert (0 ==
-                     json_object_set_new (oc->order,
-                                          "products",
-                                          json_array ()));
-    }
-    else if (! TMH_products_array_valid (jprod))
-    {
-      reply_with_error (oc,
-                        MHD_HTTP_BAD_REQUEST,
-                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                        "order.products");
-      return;
-    }
-  }
-
+  oc->merge_inventory.products = json_deep_copy(oc->parse_order.products);
   /* Populate products from inventory product array and database */
   {
-    json_t *np = json_array ();
-
-    GNUNET_assert (NULL != np);
-    for (unsigned int i = 0; i<oc->inventory_products_length; i++)
+    GNUNET_assert (NULL != oc->merge_inventory.products);
+    for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; 
i++)
     {
       struct TALER_MERCHANTDB_ProductDetails pd;
       enum GNUNET_DB_QueryStatus qs;
 
       qs = TMH_db->lookup_product (TMH_db->cls,
                                    oc->hc->instance->settings.id,
-                                   oc->inventory_products[i].product_id,
+                                   
oc->parse_request.inventory_products[i].product_id,
                                    &pd);
       if (qs <= 0)
       {
@@ -1751,7 +1919,7 @@ merge_inventory (struct OrderContext *oc)
         case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                       "Product %s from order unknown\n",
-                      oc->inventory_products[i].product_id);
+                      oc->parse_request.inventory_products[i].product_id);
           http_status = MHD_HTTP_NOT_FOUND;
           ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
           break;
@@ -1759,11 +1927,11 @@ merge_inventory (struct OrderContext *oc)
           /* case listed to make compilers happy */
           GNUNET_assert (0);
         }
-        json_decref (np);
+        json_decref (oc->merge_inventory.products);
         reply_with_error (oc,
                           http_status,
                           ec,
-                          oc->inventory_products[i].product_id);
+                          oc->parse_request.inventory_products[i].product_id);
         return;
       }
       {
@@ -1784,10 +1952,10 @@ merge_inventory (struct OrderContext *oc)
                                    pd.image),
           GNUNET_JSON_pack_uint64 (
             "quantity",
-            oc->inventory_products[i].quantity));
+            oc->parse_request.inventory_products[i].quantity));
         GNUNET_assert (NULL != p);
         GNUNET_assert (0 ==
-                       json_array_append_new (np,
+                       json_array_append_new (oc->merge_inventory.products,
                                               p));
       }
       GNUNET_free (pd.description);
@@ -1795,28 +1963,19 @@ merge_inventory (struct OrderContext *oc)
       GNUNET_free (pd.image);
       json_decref (pd.address);
     }
-    /* merge into existing products list */
-    {
-      json_t *xp;
-
-      xp = json_object_get (oc->order,
-                            "products");
-      GNUNET_assert (NULL != xp);
-      json_array_extend (xp, np);
-      json_decref (np);
-    }
   }
   oc->phase++;
 }
 
 
 /**
- * Parse the basics of the client request.
+ * Parse the client request. Upon success,
+ * continue processing by calling parse_order().
  *
  * @param[in,out] oc order context to process
  */
 static void
-parse_order_request (struct OrderContext *oc)
+parse_request (struct OrderContext *oc)
 {
   const json_t *ip = NULL;
   const json_t *uuid = NULL;
@@ -1824,14 +1983,14 @@ parse_order_request (struct OrderContext *oc)
   bool create_token = true; /* default */
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_json ("order",
-                           &oc->order),
+                           &oc->parse_request.order),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_relative_time ("refund_delay",
-                                      &oc->refund_delay),
+                                      &oc->parse_request.refund_delay),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_string ("payment_target",
-                               &oc->payment_target),
+                               &oc->parse_request.payment_target),
       NULL),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_array_const ("inventory_products",
@@ -1865,7 +2024,7 @@ parse_order_request (struct OrderContext *oc)
   }
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Refund delay is %s\n",
-              GNUNET_TIME_relative2s (oc->refund_delay,
+              GNUNET_TIME_relative2s (oc->parse_request.refund_delay,
                                       false));
   TMH_db->expire_locks (TMH_db->cls);
   if (NULL != otp_id)
@@ -1902,14 +2061,14 @@ parse_order_request (struct OrderContext *oc)
     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
       break;
     }
-    oc->pos_key = td.otp_key;
-    oc->pos_algorithm = td.otp_algorithm;
+    oc->parse_request.pos_key = td.otp_key;
+    oc->parse_request.pos_algorithm = td.otp_algorithm;
   }
   if (create_token)
   {
     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
-                                &oc->claim_token,
-                                sizeof (oc->claim_token));
+                                &oc->parse_request.claim_token,
+                                sizeof (oc->parse_request.claim_token));
   }
   /* Compute h_post_data (for idempotency check) */
   {
@@ -1932,19 +2091,19 @@ parse_order_request (struct OrderContext *oc)
     }
     GNUNET_CRYPTO_hash (req_body_enc,
                         strlen (req_body_enc),
-                        &oc->h_post_data.hash);
+                        &oc->parse_request.h_post_data.hash);
     GNUNET_free (req_body_enc);
   }
 
   /* parse the inventory_products (optionally given) */
   if (NULL != ip)
   {
-    GNUNET_array_grow (oc->inventory_products,
-                       oc->inventory_products_length,
+    GNUNET_array_grow (oc->parse_request.inventory_products,
+                       oc->parse_request.inventory_products_length,
                        json_array_size (ip));
-    for (unsigned int i = 0; i<oc->inventory_products_length; i++)
+    for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; 
i++)
     {
-      struct InventoryProduct *ipr = &oc->inventory_products[i];
+      struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i];
       const char *error_name;
       unsigned int error_line;
       struct GNUNET_JSON_Specification ispec[] = {
@@ -1980,10 +2139,10 @@ parse_order_request (struct OrderContext *oc)
   /* parse the lock_uuids (optionally given) */
   if (NULL != uuid)
   {
-    GNUNET_array_grow (oc->uuids,
-                       oc->uuids_length,
+    GNUNET_array_grow (oc->parse_request.uuids,
+                       oc->parse_request.uuids_length,
                        json_array_size (uuid));
-    for (unsigned int i = 0; i<oc->uuids_length; i++)
+    for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
     {
       json_t *ui = json_array_get (uuid,
                                    i);
@@ -2001,7 +2160,7 @@ parse_order_request (struct OrderContext *oc)
         return;
       }
       TMH_uuid_from_string (json_string_value (ui),
-                            &oc->uuids[i]);
+                            &oc->parse_request.uuids[i]);
     }
   }
   oc->phase++;
@@ -2031,8 +2190,11 @@ TMH_private_post_orders (
                 oc->phase);
     switch (oc->phase)
     {
-    case ORDER_PHASE_INIT:
-      parse_order_request (oc);
+    case ORDER_PHASE_PARSE_REQUEST:
+      parse_request (oc);
+      break;
+    case ORDER_PHASE_PARSE_ORDER:
+      parse_order (oc);
       break;
     case ORDER_PHASE_MERGE_INVENTORY:
       merge_inventory (oc);
@@ -2040,9 +2202,6 @@ TMH_private_post_orders (
     case ORDER_PHASE_ADD_PAYMENT_DETAILS:
       add_payment_details (oc);
       break;
-    case ORDER_PHASE_PATCH_ORDER:
-      patch_order (oc);
-      break;
     case ORDER_PHASE_SET_EXCHANGES:
       if (set_exchanges (oc))
         return MHD_YES;
@@ -2050,6 +2209,9 @@ TMH_private_post_orders (
     case ORDER_PHASE_SET_MAX_FEE:
       set_max_fee (oc);
       break;
+    case ORDER_PHASE_SERIALIZE_ORDER:
+      serialize_order (oc);
+      break;
     case ORDER_PHASE_CHECK_CONTRACT:
       check_contract (oc);
       break;
diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.c 
b/src/backend/taler-merchant-httpd_private-post-token-families.c
new file mode 100644
index 00000000..6bf048f9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.c
@@ -0,0 +1,244 @@
+/*
+  This file is part of TALER
+  (C) 2023 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 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 taler-merchant-httpd_private-post-token-families.c
+ * @brief implementing POST /tokenfamilies request handling
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-token-families.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Check if the two token families are identical.
+ *
+ * @param tf1 token family to compare
+ * @param tf2 other token family to compare
+ * @return true if they are 'equal', false if not
+ */
+static bool
+token_families_equal (const struct TALER_MERCHANTDB_TokenFamilyDetails *tf1,
+                      const struct TALER_MERCHANTDB_TokenFamilyDetails *tf2)
+{
+  return ( (0 == strcmp (tf1->slug,
+                         tf2->slug)) &&
+           (0 == strcmp (tf1->name,
+                         tf2->name)) &&
+           (0 == strcmp (tf1->description,
+                         tf2->description)) &&
+           (1 == json_equal (tf1->description_i18n,
+                             tf2->description_i18n)) &&
+           (GNUNET_TIME_timestamp_cmp (tf1->valid_after,
+                                       ==,
+                                       tf2->valid_after)) &&
+           (GNUNET_TIME_timestamp_cmp (tf1->valid_before,
+                                       ==,
+                                       tf2->valid_before)) &&
+           (GNUNET_TIME_relative_cmp (tf1->duration,
+                                      ==,
+                                      tf2->duration)) &&
+           (tf1->kind == tf2->kind) );
+}
+
+
+MHD_RESULT
+TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
+                                 struct MHD_Connection *connection,
+                                 struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  struct TALER_MERCHANTDB_TokenFamilyDetails details = { 0 };
+  const char *kind = NULL;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("slug",
+                             (const char **) &details.slug),
+    GNUNET_JSON_spec_string ("name",
+                             (const char **) &details.name),
+    GNUNET_JSON_spec_string ("description",
+                             (const char **) &details.description),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_json ("description_i18n",
+                             &details.description_i18n),
+      NULL),
+    GNUNET_JSON_spec_string("kind", &kind),
+    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 ()
+  };
+
+  GNUNET_assert (NULL != mi);
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+    {
+      GNUNET_break_op (0);
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+    }
+  }
+
+
+  if (strcmp(kind, "discount") == 0)
+    details.kind = TALER_MERCHANTDB_TFK_Discount;
+  else if (strcmp(kind, "subscription") == 0)
+    details.kind = TALER_MERCHANTDB_TFK_Subscription;
+  else
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       "kind");
+  }
+
+  if (NULL == details.description_i18n)
+    details.description_i18n = json_object ();
+
+  if (! TALER_JSON_check_i18n (details.description_i18n))
+  {
+    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,
+                                       "description_i18n");
+  }
+
+
+  /* finally, interact with DB until no serialization error */
+  for (unsigned int i = 0; i<MAX_RETRIES; i++)
+  {
+    /* Test if a token family of this id is known */
+    struct TALER_MERCHANTDB_TokenFamilyDetails existing;
+
+    if (GNUNET_OK !=
+        TMH_db->start (TMH_db->cls,
+                       "/post tokenfamilies"))
+    {
+      GNUNET_break (0);
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_START_FAILED,
+                                         NULL);
+    }
+    qs = TMH_db->lookup_token_family (TMH_db->cls,
+                                      mi->settings.id,
+                                      details.slug,
+                                      &existing);
+    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);
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      /* restart transaction */
+      goto retry;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      /* Good, we can proceed! */
+      break;
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      /* idempotency check: is existing == details? */
+      {
+        bool eq;
+
+        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)
+          // TODO: Use proper error code
+          : TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_CONFLICT,
+                                        0,
+                                        details.slug);
+      }
+    } /* end switch (qs) */
+
+    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);
+      break;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    {
+      qs = TMH_db->commit (TMH_db->cls);
+      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+        break;
+    }
+retry:
+    GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+    TMH_db->rollback (TMH_db->cls);
+  } /* for RETRIES loop */
+  GNUNET_JSON_parse_free (spec);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    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,
+      NULL);
+  }
+  return TALER_MHD_reply_static (connection,
+                                 MHD_HTTP_NO_CONTENT,
+                                 NULL,
+                                 NULL,
+                                 0);
+}
+
+
+/* end of taler-merchant-httpd_private-post-token-families.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.h 
b/src/backend/taler-merchant-httpd_private-post-token-families.h
similarity index 64%
copy from src/backend/taler-merchant-httpd_private-patch-accounts-ID.h
copy to src/backend/taler-merchant-httpd_private-post-token-families.h
index 752fb958..ada1c7c9 100644
--- a/src/backend/taler-merchant-httpd_private-patch-accounts-ID.h
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.h
@@ -18,17 +18,17 @@
 */
 
 /**
- * @file taler-merchant-httpd_private-patch-accounts-ID.h
- * @brief implementing PATCH /accounts request handling
- * @author Christian Grothoff
+ * @file taler-merchant-httpd_private-post-token-families.h
+ * @brief implementing POST /tokenfamilies request handling
+ * @author Christian Blättler
  */
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_ACCOUNTS_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_ACCOUNTS_ID_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_TOKEN_FAMILIES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_TOKEN_FAMILIES_H
 #include "taler-merchant-httpd.h"
 
 
 /**
- * PATCH configuration of an existing instance, given its configuration.
+ * Create a new token family.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
@@ -36,8 +36,8 @@
  * @return MHD result code
  */
 MHD_RESULT
-TMH_private_patch_accounts_ID (const struct TMH_RequestHandler *rh,
-                               struct MHD_Connection *connection,
-                               struct TMH_HandlerContext *hc);
+TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
+                                 struct MHD_Connection *connection,
+                                 struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index 339d59af..a63a56b1 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -166,6 +166,11 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
   pg_insert_pending_webhook.h pg_insert_pending_webhook.c \
   pg_update_pending_webhook.h pg_update_pending_webhook.c \
   pg_lookup_pending_webhooks.h pg_lookup_pending_webhooks.c \
+  pg_insert_token_family.h pg_insert_token_family.c \
+  pg_lookup_token_family.h pg_lookup_token_family.c \
+  pg_lookup_token_families.h pg_lookup_token_families.c \
+  pg_delete_token_family.h pg_delete_token_family.c \
+  pg_update_token_family.h pg_update_token_family.c \
   plugin_merchantdb_postgres.c \
   pg_helper.h pg_helper.c
 libtaler_plugin_merchantdb_postgres_la_LIBADD = \
diff --git a/src/backenddb/merchant-0002.sql b/src/backenddb/merchant-0002.sql
index d063ce64..33324c8e 100644
--- a/src/backenddb/merchant-0002.sql
+++ b/src/backenddb/merchant-0002.sql
@@ -14,6 +14,10 @@
 -- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 --
 
+-- @file merchant-0002.sql
+-- @brief database schema for the merchant
+-- @author Christian Blättler
+
 -- Everything in one big transaction
 BEGIN;
 
@@ -39,6 +43,101 @@ CREATE INDEX IF NOT EXISTS 
merchant_contract_terms_by_merchant_and_session
   ON merchant_contract_terms
   (merchant_serial,session_id);
 
+-------------------------- Tokens -----------------------------
+
+CREATE TABLE IF NOT EXISTS merchant_token_families
+  (token_family_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+  ,merchant_serial BIGINT NOT NULL REFERENCES merchant_instances 
(merchant_serial) ON DELETE CASCADE
+  ,slug TEXT NOT NULL UNIQUE
+  ,name TEXT NOT NULL
+  ,description TEXT
+  ,description_i18n BYTEA NOT NULL
+  ,valid_after BIGINT NOT NULL
+  ,valid_before BIGINT NOT NULL
+  ,duration BIGINT NOT NULL
+  ,kind TEXT NOT NULL CHECK (kind IN ('subscription', 'discount'))
+  ,issued BIGINT DEFAULT 0
+  ,redeemed BIGINT DEFAULT 0
+  );
+COMMENT ON TABLE merchant_token_families
+ IS 'Token families configured by the merchant.';
+COMMENT ON COLUMN merchant_token_families.merchant_serial
+ IS 'Instance where the token family is configured.';
+COMMENT ON COLUMN merchant_token_families.slug
+ IS 'Unique slug for the token family.';
+COMMENT ON COLUMN merchant_token_families.name
+ IS 'Name of the token family.';
+COMMENT ON COLUMN merchant_token_families.description
+ IS 'Human-readable description or details about the token family.';
+COMMENT ON COLUMN merchant_token_families.description_i18n
+ IS 'JSON map from IETF BCP 47 language tags to localized descriptions';
+COMMENT ON COLUMN merchant_token_families.valid_after
+ IS 'Start time of the token family''s validity period.';
+COMMENT ON COLUMN merchant_token_families.valid_before
+ IS 'End time of the token family''s validity period.';
+COMMENT ON COLUMN merchant_token_families.duration
+ IS 'Duration of the token.';
+COMMENT ON COLUMN merchant_token_families.kind
+ IS 'Kind of the token (e.g., subscription, discount).';
+COMMENT ON COLUMN merchant_token_families.issued
+ IS 'Counter for the number of tokens issued for this token family.';
+COMMENT ON COLUMN merchant_token_families.redeemed
+ IS 'Counter for the number of tokens redeemed for this token family.';
+
+
+CREATE TABLE IF NOT EXISTS merchant_token_family_keys
+  (token_family_key_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+  ,token_family_serial BIGINT REFERENCES 
merchant_token_families(token_family_serial) ON DELETE CASCADE
+  ,valid_after BIGINT NOT NULL
+  ,valid_before BIGINT NOT NULL
+  ,pub BYTEA NOT NULL
+  ,h_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(h_pub)=32)
+  ,priv BYTEA
+  ,cipher TEXT NOT NULL CHECK (cipher IN ('rsa', 'cs'))
+  ,UNIQUE (token_family_serial, valid_after)
+  );
+
+COMMENT ON TABLE merchant_token_family_keys
+ IS 'Keys for token families.';
+COMMENT ON COLUMN merchant_token_family_keys.token_family_serial
+ IS 'Token family to which the key belongs.';
+COMMENT ON COLUMN merchant_token_family_keys.valid_after
+ IS 'Start time for the validity of the token key.';
+COMMENT ON COLUMN merchant_token_family_keys.valid_before
+ IS 'Expiration time for the validity of the token key.';
+COMMENT ON COLUMN merchant_token_family_keys.pub
+ IS 'Public key of the token family.';
+COMMENT ON COLUMN merchant_token_family_keys.h_pub
+ IS 'Hash of the public key for quick lookup.';
+COMMENT ON COLUMN merchant_token_family_keys.priv
+ IS 'Private key of the token family; can be NULL if no more tokens of this 
familiy should be issued.';
+COMMENT ON COLUMN merchant_token_family_keys.cipher
+ IS 'Cipher used (rsa or cs).';
+
+
+CREATE TABLE IF NOT EXISTS merchant_spent_tokens
+  (spent_token_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+  ,merchant_serial BIGINT NOT NULL REFERENCES merchant_instances 
(merchant_serial) ON DELETE CASCADE
+  ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+  ,token_family_key_serial BIGINT REFERENCES 
merchant_token_family_keys(token_family_key_serial) ON DELETE CASCADE
+  ,token_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(token_pub)=32)
+  ,token_sig BYTEA NOT NULL CHECK (LENGTH(token_sig)=64)
+  ,blind_sig BYTEA NOT NULL
+  );
+COMMENT ON TABLE merchant_spent_tokens
+ IS 'Tokens that have been spent by customers.';
+COMMENT ON COLUMN merchant_spent_tokens.merchant_serial
+ IS 'Merchant serial where the token was spent.';
+COMMENT ON COLUMN merchant_spent_tokens.h_contract_terms
+ IS 'This is no foreign key by design.';
+COMMENT ON COLUMN merchant_spent_tokens.token_family_key_serial
+ IS 'Token family to which the spent token belongs.';
+COMMENT ON COLUMN merchant_spent_tokens.token_pub
+ IS 'Public key of the spent token.';
+COMMENT ON COLUMN merchant_spent_tokens.token_sig
+ IS 'Signature that the token was spent on specified order.';
+COMMENT ON COLUMN merchant_spent_tokens.blind_sig
+ IS 'Blind signature for the spent token to prove validity of token.';
 
 -- Complete transaction
 COMMIT;
diff --git a/src/backenddb/merchantdb_helper.c 
b/src/backenddb/merchantdb_helper.c
index 4ae75020..4ba70e4b 100644
--- a/src/backenddb/merchantdb_helper.c
+++ b/src/backenddb/merchantdb_helper.c
@@ -69,5 +69,15 @@ TALER_MERCHANTDB_pending_webhook_details_free (
   GNUNET_free (pwb->body);
 }
 
+void
+TALER_MERCHANTDB_token_family_details_free (
+  struct TALER_MERCHANTDB_TokenFamilyDetails *tf)
+{
+  GNUNET_free (tf->slug);
+  GNUNET_free (tf->name);
+  GNUNET_free (tf->description);
+  json_decref (tf->description_i18n);
+}
+
 
 /* end of merchantdb_helper.c */
diff --git a/src/backenddb/pg_delete_webhook.c 
b/src/backenddb/pg_delete_token_family.c
similarity index 68%
copy from src/backenddb/pg_delete_webhook.c
copy to src/backenddb/pg_delete_token_family.c
index ba2173cb..46a4c01f 100644
--- a/src/backenddb/pg_delete_webhook.c
+++ b/src/backenddb/pg_delete_token_family.c
@@ -14,41 +14,40 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_delete_webhook.c
- * @brief Implementation of the delete_webhook function for Postgres
- * @author Iván Ávalos
+ * @file backenddb/pg_delete_token_family.c
+ * @brief Implementation of the delete_token_family function for Postgres
+ * @author Christian Blättler
  */
 #include "platform.h"
 #include <taler/taler_error_codes.h>
 #include <taler/taler_dbevents.h>
 #include <taler/taler_pq_lib.h>
-#include "pg_delete_webhook.h"
+#include "pg_delete_token_family.h"
 #include "pg_helper.h"
 
 enum GNUNET_DB_QueryStatus
-TMH_PG_delete_webhook (void *cls,
-                       const char *instance_id,
-                       const char *webhook_id)
+TMH_PG_delete_token_family (void *cls,
+                            const char *instance_id,
+                            const char *token_family_slug)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (instance_id),
-    GNUNET_PQ_query_param_string (webhook_id),
+    GNUNET_PQ_query_param_string (token_family_slug),
     GNUNET_PQ_query_param_end
   };
 
   check_connection (pg);
   PREPARE (pg,
-           "delete_webhook",
+           "delete_token_family",
            "DELETE"
-           " FROM merchant_webhook"
-           " WHERE merchant_webhook.merchant_serial="
+           " FROM merchant_token_families"
+           " WHERE merchant_token_families.merchant_serial="
            "     (SELECT merchant_serial "
            "        FROM merchant_instances"
            "        WHERE merchant_id=$1)"
-           "   AND merchant_webhook.webhook_id=$2");
-
+           "   AND slug=$2");
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "delete_webhook",
+                                             "delete_token_family",
                                              params);
-}
+}
\ No newline at end of file
diff --git a/src/backenddb/pg_delete_login_token.h 
b/src/backenddb/pg_delete_token_family.h
similarity index 58%
copy from src/backenddb/pg_delete_login_token.h
copy to src/backenddb/pg_delete_token_family.h
index 0ae9f56b..ed380998 100644
--- a/src/backenddb/pg_delete_login_token.h
+++ b/src/backenddb/pg_delete_token_family.h
@@ -14,31 +14,28 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_delete_login_token.h
- * @brief implementation of the delete_login_token function for Postgres
- * @author Christian Grothoff
+ * @file backenddb/pg_delete_token_family.h
+ * @brief implementation of the delete_token_family function for Postgres
+ * @author Christian Blättler
  */
-#ifndef PG_DELETE_LOGIN_TOKEN_H
-#define PG_DELETE_LOGIN_TOKEN_H
+#ifndef PG_DELETE_TOKEN_FAMILY_H
+#define PG_DELETE_TOKEN_FAMILY_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
 #include "taler_merchantdb_plugin.h"
 
-
 /**
- * Delete login token from database.
- *
- * @param cls closure
- * @param id identifier of the instance
- * @param token value of the token
- * @return database result code
- */
+  * Delete information about a token family.
+  *
+  * @param cls closure
+  * @param instance_id instance to delete token family of
+  * @param token_family_slug slug of token family to delete
+  * @return database result code
+  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_delete_login_token (
-  void *cls,
-  const char *id,
-  const struct TALER_MERCHANTDB_LoginTokenP *token);
-
+TMH_PG_delete_token_family (void *cls,
+                            const char *instance_id,
+                            const char *token_family_slug);
 
 #endif
diff --git a/src/backenddb/pg_insert_token_family.c 
b/src/backenddb/pg_insert_token_family.c
new file mode 100644
index 00000000..bf7159b8
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family.c
@@ -0,0 +1,82 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2023 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_insert_token_family.c
+ * @brief Implementation of the insert_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#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)
+{
+  struct PostgresClosure *pg = cls;
+
+  const char *kind;
+  switch (details->kind)
+  {
+    case TALER_MERCHANTDB_TFK_Discount:
+      kind = "discount";
+      break;
+    case TALER_MERCHANTDB_TFK_Subscription:
+      kind = "subscription";
+      break;
+    default:
+      GNUNET_break (0);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (token_family_slug),
+    GNUNET_PQ_query_param_string (details->name),
+    GNUNET_PQ_query_param_string (details->description),
+    TALER_PQ_query_param_json (details->description_i18n),
+    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_string (kind),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  PREPARE (pg,
+           "insert_token_family",
+           "INSERT INTO merchant_token_families"
+           "(merchant_serial"
+           ",slug"
+           ",name"
+           ",description"
+           ",description_i18n"
+           ",valid_after"
+           ",valid_before"
+           ",duration"
+           ",kind)"
+           " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9"
+           " FROM merchant_instances"
+           " WHERE merchant_id=$1");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_token_family",
+                                             params);
+}
diff --git a/src/backenddb/pg_delete_login_token.h 
b/src/backenddb/pg_insert_token_family.h
similarity index 58%
copy from src/backenddb/pg_delete_login_token.h
copy to src/backenddb/pg_insert_token_family.h
index 0ae9f56b..e05755a6 100644
--- a/src/backenddb/pg_delete_login_token.h
+++ b/src/backenddb/pg_insert_token_family.h
@@ -14,12 +14,12 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_delete_login_token.h
- * @brief implementation of the delete_login_token function for Postgres
- * @author Christian Grothoff
+ * @file backenddb/pg_insert_token_family.h
+ * @brief implementation of the insert_token_family function for Postgres
+ * @author Christian Blättler
  */
-#ifndef PG_DELETE_LOGIN_TOKEN_H
-#define PG_DELETE_LOGIN_TOKEN_H
+#ifndef PG_INSERT_TOKEN_FAMILY_H
+#define PG_INSERT_TOKEN_FAMILY_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
@@ -27,18 +27,17 @@
 
 
 /**
- * Delete login token from database.
- *
  * @param cls closure
- * @param id identifier of the instance
- * @param token value of the token
+ * @param instance_id instance to insert token family for TODO: Is this needed?
+ * @param token_family_slug slug of the token family to insert
+ * @param details the token family details to insert
  * @return database result code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_delete_login_token (
-  void *cls,
-  const char *id,
-  const struct TALER_MERCHANTDB_LoginTokenP *token);
-
+TMH_PG_insert_token_family (void *cls,
+                            const char *instance_id,
+                            const char *token_family_slug,
+                            const struct TALER_MERCHANTDB_TokenFamilyDetails 
*details);
 
 #endif
+
diff --git a/src/backenddb/pg_lookup_token_families.c 
b/src/backenddb/pg_lookup_token_families.c
new file mode 100644
index 00000000..0ebe3b53
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_families.c
@@ -0,0 +1,150 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2023 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_families.c
+ * @brief Implementation of the lookup_token_families function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_token_families.h"
+#include "pg_helper.h"
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_token_families().
+ */
+struct LookupTokenFamiliesContext
+{
+  /**
+   * Function to call with the results.
+   */
+  TALER_MERCHANTDB_TokenFamiliesCallback cb;
+
+  /**
+   * Closure for @a 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 token families.
+ *
+ * @param[in,out] cls of type `struct LookupTokenFamiliesContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_token_families_cb (void *cls,
+                          PGresult *result,
+                          unsigned int num_results)
+{
+  struct LookupTokenFamiliesContext *tflc = cls;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    char *slug;
+    char *name;
+    char *kind;
+    struct GNUNET_TIME_Timestamp valid_after;
+    struct GNUNET_TIME_Timestamp valid_before;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("slug",
+                                    &slug),
+      GNUNET_PQ_result_spec_string ("name",
+                                    &name),
+      GNUNET_PQ_result_spec_timestamp ("valid_after",
+                                       &valid_after),
+      GNUNET_PQ_result_spec_timestamp ("valid_before",
+                                       &valid_before),
+      GNUNET_PQ_result_spec_string ("kind",
+                                       &kind),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      tflc->extract_failed = true;
+      return;
+    }
+
+    tflc->cb (tflc->cb_cls,
+              slug,
+              name,
+              valid_after,
+              valid_before,
+              kind);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_families (void *cls,
+                              const char *instance_id,
+                              TALER_MERCHANTDB_TokenFamiliesCallback cb,
+                              void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct LookupTokenFamiliesContext context = {
+    .cb = cb,
+    .cb_cls = cb_cls,
+    /* Can be overwritten by the lookup_token_families_cb */
+    .extract_failed = false,
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  check_connection (pg);
+  PREPARE (pg,
+           "lookup_token_families",
+           "SELECT"
+           " slug"
+           ",name"
+           ",valid_after"
+           ",valid_before"
+           ",kind"
+           " FROM merchant_token_families"
+           " JOIN merchant_instances"
+           "   USING (merchant_serial)"
+           " WHERE merchant_instances.merchant_id=$1");
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_token_families",
+                                             params,
+                                             &lookup_token_families_cb,
+                                             &context);
+  /* If there was an error inside lookup_token_families_cb, return a hard 
error. */
+  if (context.extract_failed)
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  return qs;
+}
diff --git a/src/backenddb/pg_select_account_by_uri.h 
b/src/backenddb/pg_lookup_token_families.h
similarity index 60%
copy from src/backenddb/pg_select_account_by_uri.h
copy to src/backenddb/pg_lookup_token_families.h
index 718209be..0c9f80fe 100644
--- a/src/backenddb/pg_select_account_by_uri.h
+++ b/src/backenddb/pg_lookup_token_families.h
@@ -14,31 +14,30 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_select_account_by_uri.h
- * @brief implementation of the select_account_by_uri function for Postgres
+ * @file backenddb/pg_lookup_token_families.h
+ * @brief implementation of the lookup_token_families function for Postgres
  * @author Christian Grothoff
  */
-#ifndef PG_SELECT_ACCOUNT_BY_URI_H
-#define PG_SELECT_ACCOUNT_BY_URI_H
+#ifndef PG_LOOKUP_TOKEN_FAMILIES_H
+#define PG_LOOKUP_TOKEN_FAMILIES_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
 #include "taler_merchantdb_plugin.h"
 
-
 /**
- * Obtain information about an instance's accounts.
+ * Lookup all of the token families the given instance has configured.
  *
  * @param cls closure
- * @param id identifier of the instance
- * @param payto_uri URI of the account
- * @param[out] ad account details returned
+ * @param instance_id instance to lookup token families for
+ * @param cb function to call on all token families found
+ * @param cb_cls closure for @a cb
  * @return database result code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_select_account_by_uri (void *cls,
-                              const char *id,
-                              const char *payto_uri,
-                              struct TALER_MERCHANTDB_AccountDetails *ad);
+TMH_PG_lookup_token_families (void *cls,
+                              const char *instance_id,
+                              TALER_MERCHANTDB_TokenFamiliesCallback cb,
+                              void *cb_cls);
 
 #endif
diff --git a/src/backenddb/pg_lookup_token_family.c 
b/src/backenddb/pg_lookup_token_family.c
new file mode 100644
index 00000000..848b79a9
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family.c
@@ -0,0 +1,121 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2023 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.c
+ * @brief Implementation of the lookup_token_family 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.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)
+{
+  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_end
+  };
+
+  if (NULL == details)
+  {
+    struct GNUNET_PQ_ResultSpec rs_null[] = {
+      GNUNET_PQ_result_spec_end
+    };
+
+    check_connection (pg);
+    return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                     "lookup_token_family",
+                                                     params,
+                                                     rs_null);
+  }
+  else
+  {
+    char *kind;
+
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("slug",
+                                    &details->slug),
+      GNUNET_PQ_result_spec_string ("name",
+                                    &details->name),
+      GNUNET_PQ_result_spec_string ("description",
+                                    &details->description),
+      TALER_PQ_result_spec_json ("description_i18n",
+                                 &details->description_i18n),
+      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_string ("kind",
+                                    &kind),
+      GNUNET_PQ_result_spec_uint64 ("issued",
+                                    &details->issued),
+      GNUNET_PQ_result_spec_uint64 ("redeemed",
+                                    &details->redeemed),
+      GNUNET_PQ_result_spec_end
+    };
+
+    check_connection (pg);
+    PREPARE (pg,
+             "lookup_token_family",
+             "SELECT"
+             " slug"
+             ",name"
+             ",description"
+             ",description_i18n"
+             ",valid_after"
+             ",valid_before"
+             ",duration"
+             ",kind"
+             ",issued"
+             ",redeemed"
+             " FROM merchant_token_families"
+             " JOIN merchant_instances"
+             "   USING (merchant_serial)"
+             " WHERE merchant_instances.merchant_id=$1"
+             "   AND merchant_token_families.slug=$2");
+    enum GNUNET_DB_QueryStatus qs;
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_token_family",
+                                                   params,
+                                                   rs);
+
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    {
+      if (strcmp(kind, "discount") == 0)
+        details->kind = TALER_MERCHANTDB_TFK_Discount;
+      else if (strcmp(kind, "subscription") == 0)
+        details->kind = TALER_MERCHANTDB_TFK_Subscription;
+      else
+      {
+        GNUNET_break (0);
+        return GNUNET_DB_STATUS_HARD_ERROR;
+      }
+    }
+
+    return qs;
+  }
+}
diff --git a/src/backenddb/pg_delete_pending_webhook.h 
b/src/backenddb/pg_lookup_token_family.h
similarity index 54%
copy from src/backenddb/pg_delete_pending_webhook.h
copy to src/backenddb/pg_lookup_token_family.h
index 1247cf4e..4a1b1872 100644
--- a/src/backenddb/pg_delete_pending_webhook.h
+++ b/src/backenddb/pg_lookup_token_family.h
@@ -14,27 +14,31 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_delete_pending_webhook.h
- * @brief implementation of the delete_pending_webhook function for Postgres
- * @author Iván Ávalos
+ * @file backenddb/pg_lookup_token_family.h
+ * @brief implementation of the lookup_token_family function for Postgres
+ * @author Christian Blättler
  */
-#ifndef PG_DELETE_PENDING_WEBHOOK_H
-#define PG_DELETE_PENDING_WEBHOOK_H
+#ifndef PG_LOOKUP_TOKEN_FAMILY_H
+#define PG_LOOKUP_TOKEN_FAMILY_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
 #include "taler_merchantdb_plugin.h"
 
 /**
- * Delete a webhook in the pending webhook after the
- * webhook was completed successfully.
+ * Lookup details about a particular token family.
  *
  * @param cls closure
- * @param webhook_pending_serial identifies the row that needs to be deleted 
in the pending webhook table
+ * @param instance_id instance to lookup token family for
+ * @param token_family_slug token family to lookup
+ * @param[out] details set to the token family details on success, can be NULL
+ *             (in that case we only want to check if the token family exists)
  * @return database result code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_delete_pending_webhook (void *cls,
-                               uint64_t webhook_pending_serial);
+TMH_PG_lookup_token_family (void *cls,
+                            const char *instance_id,
+                            const char *token_family_slug,
+                            struct TALER_MERCHANTDB_TokenFamilyDetails 
*details);
 
 #endif
diff --git a/src/backenddb/pg_update_webhook.c 
b/src/backenddb/pg_update_token_family.c
similarity index 53%
copy from src/backenddb/pg_update_webhook.c
copy to src/backenddb/pg_update_token_family.c
index c12ae9e3..7864c60b 100644
--- a/src/backenddb/pg_update_webhook.c
+++ b/src/backenddb/pg_update_token_family.c
@@ -14,56 +14,53 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_update_webhook.c
- * @brief Implementation of the update_webhook function for Postgres
- * @author Iván Ávalos
+ * @file backenddb/pg_update_token_family.c
+ * @brief Implementation of the update_token_family function for Postgres
+ * @author Christian Blättler
  */
 #include "platform.h"
 #include <taler/taler_error_codes.h>
 #include <taler/taler_dbevents.h>
 #include <taler/taler_pq_lib.h>
-#include "pg_update_webhook.h"
+#include "pg_update_token_family.h"
 #include "pg_helper.h"
 
 enum GNUNET_DB_QueryStatus
-TMH_PG_update_webhook (void *cls,
-                       const char *instance_id,
-                       const char *webhook_id,
-                       const struct TALER_MERCHANTDB_WebhookDetails *wb)
+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;
+
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (instance_id),
-    GNUNET_PQ_query_param_string (webhook_id),
-    GNUNET_PQ_query_param_string (wb->event_type),
-    GNUNET_PQ_query_param_string (wb->url),
-    GNUNET_PQ_query_param_string (wb->http_method),
-    (NULL == wb->header_template)
-    ? GNUNET_PQ_query_param_null ()
-    : GNUNET_PQ_query_param_string (wb->header_template),
-    (NULL == wb->body_template)
-    ? GNUNET_PQ_query_param_null ()
-    : GNUNET_PQ_query_param_string (wb->body_template),
+    GNUNET_PQ_query_param_string (token_family_slug),
+    GNUNET_PQ_query_param_string (details->name),
+    GNUNET_PQ_query_param_string (details->description),
+    TALER_PQ_query_param_json (details->description_i18n),
+    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
   };
 
-
   check_connection (pg);
   PREPARE (pg,
-           "update_webhook",
-           "UPDATE merchant_webhook SET"
-           " event_type=$3"
-           ",url=$4"
-           ",http_method=$5"
-           ",header_template=$6"
-           ",body_template=$7"
+           "update_token_family",
+           "UPDATE merchant_token_families SET"
+           " name=$3"
+           ",description=$4"
+           ",description_i18n=$5"
+           ",valid_after=$6"
+           ",valid_before=$7"
+           ",duration=$8"
            " WHERE merchant_serial="
            "   (SELECT merchant_serial"
            "      FROM merchant_instances"
            "      WHERE merchant_id=$1)"
-           "   AND webhook_id=$2");
-
+           "   AND slug=$2");
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "update_webhook",
+                                             "update_token_family",
                                              params);
 }
diff --git a/src/backenddb/pg_lookup_order_status.h 
b/src/backenddb/pg_update_token_family.h
similarity index 51%
copy from src/backenddb/pg_lookup_order_status.h
copy to src/backenddb/pg_update_token_family.h
index acf8fe31..84ce65ec 100644
--- a/src/backenddb/pg_lookup_order_status.h
+++ b/src/backenddb/pg_update_token_family.h
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2023 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
@@ -14,32 +14,31 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_lookup_order_status.h
- * @brief implementation of the lookup_order_status function for Postgres
- * @author Iván Ávalos
+ * @file backenddb/pg_update_token_family.h
+ * @brief implementation of the update_token_family function for Postgres
+ * @author Christian Blättler
  */
-#ifndef PG_LOOKUP_ORDER_STATUS_H
-#define PG_LOOKUP_ORDER_STATUS_H
+#ifndef PG_UPDATE_TOKEN_FAMILY_H
+#define PG_UPDATE_TOKEN_FAMILY_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
 #include "taler_merchantdb_plugin.h"
 
 /**
- * Retrieve contract terms given its @a order_id
- *
- * @param cls closure
- * @param instance_id instance's identifier
- * @param order_id order to lookup contract for
- * @param[out] h_contract_terms set to the hash of the contract.
- * @param[out] paid set to the payment status of the contract
- * @return transaction status
- */
+  * Update details about a particular token family.
+  *
+  * @param cls closure
+  * @param instance_id instance to update token family for
+  * @param token_family_slug slug of token family to update
+  * @param details set to the updated token family on success, can be NULL
+  *        (in that case we only want to check if the token family exists)
+  * @return database result code
+  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_lookup_order_status (void *cls,
+TMH_PG_update_token_family (void *cls,
                             const char *instance_id,
-                            const char *order_id,
-                            struct TALER_PrivateContractHashP 
*h_contract_terms,
-                            bool *paid);
+                            const char *token_family_slug,
+                            const struct TALER_MERCHANTDB_TokenFamilyDetails 
*details);
 
 #endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 1f1f54c6..38dd636e 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -147,6 +147,11 @@
 #include "pg_activate_reserve.h"
 #include "pg_authorize_reward.h"
 #include "pg_insert_pickup.h"
+#include "pg_insert_token_family.h"
+#include "pg_lookup_token_family.h"
+#include "pg_lookup_token_families.h"
+#include "pg_delete_token_family.h"
+#include "pg_update_token_family.h"
 
 
 /**
@@ -601,6 +606,19 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
     = &TMH_PG_select_accounts_by_exchange;
   plugin->insert_exchange_account
     = &TMH_PG_insert_exchange_account;
+  plugin->insert_token_family
+    = &TMH_PG_insert_token_family;
+  plugin->lookup_token_family
+    = &TMH_PG_lookup_token_family;
+  plugin->lookup_token_families
+    = &TMH_PG_lookup_token_families;
+  plugin->delete_token_family
+    = &TMH_PG_delete_token_family;
+  plugin->update_token_family
+    = &TMH_PG_update_token_family;
+
+
+
   return plugin;
 }
 
diff --git a/src/include/taler_merchantdb_lib.h 
b/src/include/taler_merchantdb_lib.h
index 1120b864..3a641a54 100644
--- a/src/include/taler_merchantdb_lib.h
+++ b/src/include/taler_merchantdb_lib.h
@@ -87,6 +87,16 @@ void
 TALER_MERCHANTDB_pending_webhook_details_free (
   struct TALER_MERCHANTDB_PendingWebhookDetails *pwb);
 
+
+/**
+ * Free members of @a tf, but not @a tf itself.
+ *
+ * @param[in] tf token family details to clean up
+ */
+void
+TALER_MERCHANTDB_token_family_details_free (
+  struct TALER_MERCHANTDB_TokenFamilyDetails *tf);
+
 #endif  /* MERCHANT_DB_H */
 
 /* end of taler_merchantdb_lib.h */
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 8aea7aac..8df171fe 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -1072,6 +1072,152 @@ struct TALER_MERCHANTDB_PickupDetails
 
 };
 
+/**
+ * Possible token family kinds.
+ */
+enum TALER_MERCHANTDB_TokenFamilyKind
+{
+
+  /**
+   * Token family representing a discount token
+   */
+  TALER_MERCHANTDB_TFK_Discount = 0,
+
+  /**
+   * Token family representing a subscription token
+   */
+  TALER_MERCHANTDB_TFK_Subscription = 1,
+
+};
+
+
+/**
+ * Typically called by `lookup_token_families`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param slug slug of the token family
+ * @param name name of the token family
+ * @param start_time start time of the token family's validity period
+ * @param expiration end time of the token family's validity period
+ */
+typedef void
+(*TALER_MERCHANTDB_TokenFamiliesCallback)(void *cls,
+                                          const char *slug,
+                                          const char *name,
+                                          struct GNUNET_TIME_Timestamp 
start_time,
+                                          struct GNUNET_TIME_Timestamp 
expiration,
+                                          const char *kind);
+
+
+/**
+ * Details about a token family.
+ */
+struct TALER_MERCHANTDB_TokenFamilyDetails
+{
+  /**
+   * Token family slug used for identification.
+   */
+  char* slug;
+
+  /**
+   * User readable name of the token family.
+   */
+  char* name;
+
+  /**
+   * Description of the token family.
+   */
+  char* description;
+
+  /**
+   * Internationalized token family description.
+   */
+  json_t *description_i18n;
+
+  /**
+   * Start time of the token family duration.
+   */
+  struct GNUNET_TIME_Timestamp valid_after;
+
+  /**
+   * End time of the token family duration.
+   */
+  struct GNUNET_TIME_Timestamp valid_before;
+
+  /**
+   * Validity duration of the token family.
+   */
+  struct GNUNET_TIME_Relative duration;
+
+  /**
+   * Token family kind.
+   */
+  enum TALER_MERCHANTDB_TokenFamilyKind kind;
+
+  /**
+   * Counter for each issued token of this family.
+   */
+  uint64_t issued;
+
+  /**
+   * Counter for each redeemed token of this family.
+   */
+  uint64_t redeemed;
+};
+
+
+/**
+ * Details about a token key.
+ */
+struct TALER_MERCHANTDB_TokenFamilyKeyDetails
+{
+  /**
+   * Tokens signed with this key are valid from this date on.
+   */
+  struct GNUNET_TIME_Timestamp valid_after;
+
+  /**
+   * Tokens signed with this key are valid until this date.
+   */
+  struct GNUNET_TIME_Timestamp valid_before;
+
+  /**
+   * Token family public key.
+   */
+  struct TALER_TokenFamilyPublicKey pub;
+
+  /**
+   * Hash of the token family public key.
+   */
+  struct TALER_TokenFamilyPublicKeyHash pub_h;
+
+  /**
+   * Token family private key.
+  */
+  struct TALER_TokenFamilyPrivateKey priv;
+};
+
+/**
+ * Details about a spent token.
+*/
+struct TALER_MERCHANTDB_SpentTokenDetails
+{
+  /**
+   * Public key of the spent token.
+  */
+  struct TALER_TokenPublicKey pub;
+
+  /**
+   * Signature that this token was spent on the specified order.
+  */
+  struct TALER_TokenSignature sig;
+
+  /**
+   * Blind signature for the spent token to prove validity of it.
+  */
+  struct TALER_TokenBlindSignature blind_sig;
+};
+
 
 /**
  * Handle to interact with the database.
@@ -3418,6 +3564,83 @@ struct TALER_MERCHANTDB_Plugin
   (*insert_exchange_keys)(void *cls,
                           const struct TALER_EXCHANGE_Keys *keys);
 
+
+  /**
+   * Lookup all of the token families the given instance has configured.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup token families for
+   * @param cb function to call on all token families found
+   * @param cb_cls closure for @a cb
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_token_families)(void *cls,
+                           const char *instance_id,
+                           TALER_MERCHANTDB_TokenFamiliesCallback cb,
+                           void *cb_cls);
+
+  /**
+   * Lookup details about a particular token family.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup token family for
+   * @param token_family_slug token family to lookup
+   * @param[out] details set to the token family details on success, can be 
NULL
+   *             (in that case we only want to check if the token family 
exists)
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_token_family)(void *cls,
+                         const char *instance_id,
+                         const char *token_family_slug,
+                         struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+  /**
+   * Delete information about a token family.
+   *
+   * @param cls closure
+   * @param instance_id instance to delete token family of
+   * @param token_family_slug slug of token family to delete
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*delete_token_family)(void *cls,
+                         const char *instance_id,
+                         const char *token_family_slug);
+
+  /**
+   * Update details about a particular token family.
+   *
+   * @param cls closure
+   * @param instance_id instance to update token family for
+   * @param token_family_slug slug of token family to update
+   * @param details set to the updated token family on success, can be NULL
+   *        (in that case we only want to check if the token family exists)
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*update_token_family)(void *cls,
+                         const char *instance_id,
+                         const char *token_family_slug,
+                         const struct TALER_MERCHANTDB_TokenFamilyDetails 
*details);
+
+
+  /**
+   * Insert details about a particular token family.
+   *
+   * @param cls closure
+   * @param instance_id instance to insert product for
+   * @param token_family_slug slug of token family to insert
+   * @param details the token family details to insert
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_token_family)(void *cls,
+                         const char *instance_id,
+                         const char *token_family_slug,
+                         const struct TALER_MERCHANTDB_TokenFamilyDetails 
*details);
+
 };
 
 #endif
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index 1fb5394c..2a51c29f 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -527,7 +527,7 @@ run (void *cls,
                                            MHD_HTTP_OK),
     TALER_TESTING_cmd_merchant_forget_order ("forget-1",
                                              merchant_url,
-                                             MHD_HTTP_OK,
+                                             MHD_HTTP_NO_CONTENT,
                                              "create-proposal-1",
                                              NULL,
                                              "$.dummy_obj",
@@ -558,7 +558,7 @@ run (void *cls,
                                              NULL),
     TALER_TESTING_cmd_merchant_forget_order ("forget-order-array-elem",
                                              merchant_url,
-                                             MHD_HTTP_OK,
+                                             MHD_HTTP_NO_CONTENT,
                                              "create-proposal-1",
                                              NULL,
                                              "$.dummy_array[0].item",
@@ -575,7 +575,7 @@ run (void *cls,
                                            MHD_HTTP_OK),
     TALER_TESTING_cmd_merchant_forget_order ("forget-order-array-wc",
                                              merchant_url,
-                                             MHD_HTTP_OK,
+                                             MHD_HTTP_NO_CONTENT,
                                              "create-proposal-1",
                                              NULL,
                                              "$.dummy_array[*].item",
diff --git a/src/testing/testing_api_cmd_forget_order.c 
b/src/testing/testing_api_cmd_forget_order.c
index 9dc3b1b4..76d2427b 100644
--- a/src/testing/testing_api_cmd_forget_order.c
+++ b/src/testing/testing_api_cmd_forget_order.c
@@ -120,7 +120,12 @@ order_forget_cb (void *cls,
 
   ofs->ofh = NULL;
   if (ofs->http_status != hr->http_status)
-    TALER_TESTING_FAIL (ofs->is);
+  {
+    TALER_TESTING_unexpected_status (ofs->is,
+                                     hr->http_status,
+                                     ofs->http_status);
+    return;
+  }
   TALER_TESTING_interpreter_next (ofs->is);
 }
 
diff --git a/src/testing/testing_api_cmd_post_templates.c 
b/src/testing/testing_api_cmd_post_templates.c
index 8c8cd8ca..0ff0d11a 100644
--- a/src/testing/testing_api_cmd_post_templates.c
+++ b/src/testing/testing_api_cmd_post_templates.c
@@ -92,12 +92,9 @@ post_templates_cb (void *cls,
   tis->iph = NULL;
   if (tis->http_status != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u (%d) to command %s\n",
-                hr->http_status,
-                (int) hr->ec,
-                TALER_TESTING_interpreter_get_current_label (tis->is));
-    TALER_TESTING_interpreter_fail (tis->is);
+    TALER_TESTING_unexpected_status (tis->is,
+                                     hr->http_status,
+                                     tis->http_status);
     return;
   }
   switch (hr->http_status)

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