gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] 09/19: POST /tokenfamilies endpoint


From: gnunet
Subject: [taler-merchant] 09/19: POST /tokenfamilies endpoint
Date: Fri, 22 Dec 2023 17:25:08 +0100

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

grothoff pushed a commit to branch master
in repository merchant.

commit 094327bc906cdeced19a52c86dc0725aad773997
Author: Christian Blättler <blatc2@bfh.ch>
AuthorDate: Tue Nov 21 08:06:00 2023 +0100

    POST /tokenfamilies endpoint
---
 src/backend/Makefile.am                            |  10 +
 src/backend/taler-merchant-httpd.c                 |  41 ++++
 ...hant-httpd_private-delete-token-families-SLUG.c |  73 +++++++
 ...hant-httpd_private-delete-token-families-SLUG.h |  41 ++++
 .../taler-merchant-httpd_private-get-templates.c   |   3 +-
 ...erchant-httpd_private-get-token-families-SLUG.c | 104 ++++++++++
 ...erchant-httpd_private-get-token-families-SLUG.h |  41 ++++
 ...ler-merchant-httpd_private-get-token-families.c |  85 ++++++++
 ...ler-merchant-httpd_private-get-token-families.h |  41 ++++
 ...aler-merchant-httpd_private-patch-products-ID.h |   2 +-
 ...chant-httpd_private-patch-token-families-SLUG.c | 158 ++++++++++++++
 ...hant-httpd_private-patch-token-families-SLUG.h} |  20 +-
 ...er-merchant-httpd_private-post-token-families.c | 229 +++++++++++++++++++++
 ...r-merchant-httpd_private-post-token-families.h} |  20 +-
 src/backenddb/Makefile.am                          |   2 +
 src/backenddb/merchant-0002.sql                    |  45 ++--
 src/backenddb/merchantdb_helper.c                  |  10 +
 src/backenddb/pg_insert_token_family.c             |  81 ++++++++
 src/backenddb/pg_insert_token_family.h             |  43 ++++
 src/backenddb/pg_lookup_token_family.c             | 101 +++++++++
 src/backenddb/pg_lookup_token_family.h             |  44 ++++
 src/backenddb/plugin_merchantdb_postgres.c         |   8 +
 src/include/taler_merchantdb_plugin.h              | 108 +++++++++-
 23 files changed, 1266 insertions(+), 44 deletions(-)

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-token-families-SLUG.c 
b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c
new file mode 100644
index 00000000..3418b795
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.c
@@ -0,0 +1,73 @@
+/*
+  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-delete-token-families-SLUG.c
+ * @brief implement DELETE /tokenfamilies/$SLUG
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-token-families-SLUG.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/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_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;
+
+  (void) rh;
+  GNUNET_assert (NULL != mi);
+  GNUNET_assert (NULL != 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_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_token_family (soft)");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  GNUNET_assert (0);
+  return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-token-families-SLUG.c */
diff --git 
a/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h 
b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h
new file mode 100644
index 00000000..e8b72fc6
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-token-families-SLUG.h
@@ -0,0 +1,41 @@
+/*
+  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-delete-token-families-SLUG.h
+ * @brief implement DELETE /tokenfamilies/$SLUG/
+ * @author Christian Blättler
+ */
+#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 "/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_delete_token_families_SLUG (const struct TMH_RequestHandler *rh,
+                                        struct MHD_Connection *connection,
+                                        struct TMH_HandlerContext *hc);
+
+/* 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..d31884da
--- /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);
+  }
+  // TODO: Why is this block here? Limiting variable lifetime?
+  {
+    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);
+    return result;
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-get-products-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h 
b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h
new file mode 100644
index 00000000..a7b02d8f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.h
@@ -0,0 +1,41 @@
+/*
+  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.h
+ * @brief implement GET /tokenfamilies/$SLUG/
+ * @author Christian Blättler
+ */
+#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 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);
+
+/* 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..031edd90
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families.c
@@ -0,0 +1,85 @@
+/*
+  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 start_time start time of the token family's validity period
+ * @param expiration 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 start_time,
+                  struct GNUNET_TIME_Timestamp expiration)
+{
+  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 ("start_time", start_time),
+                     GNUNET_JSON_pack_timestamp ("expiration", expiration))));
+}
+
+
+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-get-token-families.h 
b/src/backend/taler-merchant-httpd_private-get-token-families.h
new file mode 100644
index 00000000..a02a42b0
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-token-families.h
@@ -0,0 +1,41 @@
+/*
+  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.h
+ * @brief implement GET /tokenfamilies
+ * @author Christian Blättler
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TOKENFAMILIES_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/tokenfamilies" 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 (const struct TMH_RequestHandler *rh,
+                               struct MHD_Connection *connection,
+                               struct TMH_HandlerContext *hc);
+
+/* 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-token-families-SLUG.c 
b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
new file mode 100644
index 00000000..a5fef59d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.c
@@ -0,0 +1,158 @@
+/*
+  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-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-token-families-SLUG.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
+
+/**
+ * Handle a PATCH "/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_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 *slug = hc->infix;
+  struct TALER_MERCHANTDB_TokenFamilyDetails details = {0};
+  int64_t total_stock;
+  enum GNUNET_DB_QueryStatus qs;
+  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 (
+      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 != slug);
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+
+  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;
+
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      ret = TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        TALER_EC_GENERIC_DB_STORE_FAILED,
+                                        NULL);
+      break;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      ret = TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                                        "unexpected serialization problem");
+      break;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      // 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,
+                                    MHD_HTTP_NO_CONTENT,
+                                    NULL,
+                                    NULL,
+                                    0);
+      break;
+    }
+    GNUNET_JSON_parse_free (spec);
+    return ret;
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-patch-token-families-SLUG.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.h 
b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
similarity index 61%
copy from src/backend/taler-merchant-httpd_private-patch-products-ID.h
copy to src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
index e7f8fcfd..87ad86b3 100644
--- a/src/backend/taler-merchant-httpd_private-patch-products-ID.h
+++ b/src/backend/taler-merchant-httpd_private-patch-token-families-SLUG.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2020 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
@@ -18,17 +18,17 @@
 */
 
 /**
- * @file taler-merchant-httpd_private-patch-products-ID.h
- * @brief implementing POST /products 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_PRODUCTS_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_PRODUCTS_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_products_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-token-families.c 
b/src/backend/taler-merchant-httpd_private-post-token-families.c
new file mode 100644
index 00000000..6ff25942
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.c
@@ -0,0 +1,229 @@
+/*
+  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 };
+  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_uint32("kind", &details.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 (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-products-ID.h 
b/src/backend/taler-merchant-httpd_private-post-token-families.h
similarity index 62%
copy from src/backend/taler-merchant-httpd_private-patch-products-ID.h
copy to src/backend/taler-merchant-httpd_private-post-token-families.h
index e7f8fcfd..ada1c7c9 100644
--- a/src/backend/taler-merchant-httpd_private-patch-products-ID.h
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2020 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
@@ -18,17 +18,17 @@
 */
 
 /**
- * @file taler-merchant-httpd_private-patch-products-ID.h
- * @brief implementing POST /products 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_PRODUCTS_ID_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_PRODUCTS_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_products_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..f0e2bc9c 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -166,6 +166,8 @@ 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 \
   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 c2fffb43..b92a1fcf 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;
 
@@ -43,9 +47,12 @@ CREATE INDEX IF NOT EXISTS 
merchant_contract_terms_by_merchant_and_session
 
 CREATE TABLE IF NOT EXISTS merchant_token_families
   (token_family_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
-  ,name TEXT NOT NULL UNIQUE
+  ,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
@@ -53,14 +60,20 @@ CREATE TABLE IF NOT EXISTS merchant_token_families
   );
 COMMENT ON TABLE merchant_token_families
  IS 'Token families configured by the merchant.';
+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 family.';
+ 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
@@ -69,8 +82,8 @@ 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_keys
-  (token_keys_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+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
@@ -81,21 +94,21 @@ CREATE TABLE IF NOT EXISTS merchant_token_keys
   ,UNIQUE (token_family_serial, valid_after)
   );
 
-COMMENT ON TABLE merchant_token_keys
+COMMENT ON TABLE merchant_token_family_keys
  IS 'Keys for token families.';
-COMMENT ON COLUMN merchant_token_keys.token_family_serial
+COMMENT ON COLUMN merchant_token_family_keys.token_family_serial
  IS 'Token family to which the key belongs.';
-COMMENT ON COLUMN merchant_token_keys.valid_after
- IS 'Start date/time for the validity of the token key.';
-COMMENT ON COLUMN merchant_token_keys.valid_before
- IS 'Expiration date/time for the validity of the token key.';
-COMMENT ON COLUMN merchant_token_keys.pub
+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_keys.h_pub
+COMMENT ON COLUMN merchant_token_family_keys.h_pub
  IS 'Hash of the public key for quick lookup.';
-COMMENT ON COLUMN merchant_token_keys.priv
+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_keys.cipher
+COMMENT ON COLUMN merchant_token_family_keys.cipher
  IS 'Cipher used (rsa or cs).';
 
 
@@ -103,7 +116,7 @@ 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_key_serial BIGINT REFERENCES merchant_token_key(token_key_serial) ON 
DELETE CASCADE
+  ,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
@@ -114,7 +127,7 @@ 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_key_serial
+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.';
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_insert_token_family.c 
b/src/backenddb/pg_insert_token_family.c
new file mode 100644
index 00000000..88cd8649
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family.c
@@ -0,0 +1,81 @@
+/*
+   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";
+    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"
+           "(slug"
+           ",name"
+           ",description"
+           ",description_i18n"
+           ",valid_after"
+           ",valid_before"
+           ",duration"
+           ",kind)"
+           "VALUES"
+           "($1, $2, $3, $4, $5, $6, $7, $8)");
+          //  " 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_insert_token_family.h 
b/src/backenddb/pg_insert_token_family.h
new file mode 100644
index 00000000..e05755a6
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family.h
@@ -0,0 +1,43 @@
+/*
+   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.h
+ * @brief implementation of the insert_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_INSERT_TOKEN_FAMILY_H
+#define PG_INSERT_TOKEN_FAMILY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * @param cls closure
+ * @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_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_family.c 
b/src/backenddb/pg_lookup_token_family.c
new file mode 100644
index 00000000..7a871692
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family.c
@@ -0,0 +1,101 @@
+/*
+   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
+  {
+    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),
+      // TODO: How to convert kind string into enum 
TALER_MERCHANTDB_TokenFamilyKind
+      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"
+             ",issued"
+             ",redeemed"
+             " FROM merchant_token_families"
+            //  " JOIN merchant_instances"
+            //  "   USING (merchant_serial)"
+            //  " WHERE merchant_instances.merchant_id=$1"
+             "   WHERE merchant_token_families.slug=$1");
+    return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                     "lookup_token_family",
+                                                     params,
+                                                     rs);
+  }
+}
diff --git a/src/backenddb/pg_lookup_token_family.h 
b/src/backenddb/pg_lookup_token_family.h
new file mode 100644
index 00000000..4a1b1872
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family.h
@@ -0,0 +1,44 @@
+/*
+   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.h
+ * @brief implementation of the lookup_token_family function for Postgres
+ * @author Christian Blättler
+ */
+#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"
+
+/**
+ * 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
+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/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 1f1f54c6..90586561 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -147,6 +147,8 @@
 #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"
 
 
 /**
@@ -601,6 +603,12 @@ 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;
+
+
   return plugin;
 }
 
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 631cdcd5..c838a175 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -255,6 +255,22 @@ typedef void
 (*TALER_MERCHANTDB_ProductsCallback)(void *cls,
                                      const char *product_id);
 
+/**
+ * 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);
+
 
 /**
  * Details about a product.
@@ -1115,6 +1131,16 @@ struct TALER_MERCHANTDB_TokenFamilyDetails
    */
   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.
    */
@@ -1166,11 +1192,6 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails
    * Token family private key.
   */
   struct TALER_TokenFamilyPrivateKey priv;
-
-  /**
-   * Token family key cipher.
-   */
-  // enum TALER_MERCHANTDB_TokenKeyCipher cipher;
 };
 
 /**
@@ -3540,6 +3561,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

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