merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-private-tokenfamilies.c (14526B)


      1 /*
      2   This file is part of TALER
      3   (C) 2023, 2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_post-private-tokenfamilies.c
     22  * @brief implementing POST /tokenfamilies request handling
     23  * @author Christian Blättler
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_post-private-tokenfamilies.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include <gnunet/gnunet_time_lib.h>
     29 #include <taler/taler_json_lib.h>
     30 #include "merchant-database/insert_token_family.h"
     31 #include "merchant-database/lookup_token_family.h"
     32 #include "merchant-database/preflight.h"
     33 #include "merchant-database/start.h"
     34 
     35 
     36 /**
     37  * How often do we retry the simple INSERT database transaction?
     38  */
     39 #define MAX_RETRIES 3
     40 
     41 
     42 /**
     43  * Check if the two token families are identical.
     44  *
     45  * @param tf1 token family to compare
     46  * @param tf2 other token family to compare
     47  * @return true if they are 'equal', false if not
     48  */
     49 static bool
     50 token_families_equal (const struct TALER_MERCHANTDB_TokenFamilyDetails *tf1,
     51                       const struct TALER_MERCHANTDB_TokenFamilyDetails *tf2)
     52 {
     53   /* Note: we're not comparing 'cipher', as that is selected
     54      in the database to some default value and we currently
     55      do not allow the SPA to change it. As a result, it should
     56      always be "NULL" in tf1 and the DB-default in tf2. */
     57   return ( (0 == strcmp (tf1->slug,
     58                          tf2->slug)) &&
     59            (0 == strcmp (tf1->name,
     60                          tf2->name)) &&
     61            (0 == strcmp (tf1->description,
     62                          tf2->description)) &&
     63            (1 == json_equal (tf1->description_i18n,
     64                              tf2->description_i18n)) &&
     65            ( (tf1->extra_data == tf2->extra_data) ||
     66              (1 == json_equal (tf1->extra_data,
     67                                tf2->extra_data)) ) &&
     68            (GNUNET_TIME_timestamp_cmp (tf1->valid_after,
     69                                        ==,
     70                                        tf2->valid_after)) &&
     71            (GNUNET_TIME_timestamp_cmp (tf1->valid_before,
     72                                        ==,
     73                                        tf2->valid_before)) &&
     74            (GNUNET_TIME_relative_cmp (tf1->duration,
     75                                       ==,
     76                                       tf2->duration)) &&
     77            (GNUNET_TIME_relative_cmp (tf1->validity_granularity,
     78                                       ==,
     79                                       tf2->validity_granularity)) &&
     80            (GNUNET_TIME_relative_cmp (tf1->start_offset,
     81                                       ==,
     82                                       tf2->start_offset)) &&
     83            (tf1->kind == tf2->kind) );
     84 }
     85 
     86 
     87 enum MHD_Result
     88 TMH_private_post_token_families (const struct TMH_RequestHandler *rh,
     89                                  struct MHD_Connection *connection,
     90                                  struct TMH_HandlerContext *hc)
     91 {
     92   struct TMH_MerchantInstance *mi = hc->instance;
     93   struct TALER_MERCHANTDB_TokenFamilyDetails details = { 0 };
     94   const char *kind = NULL;
     95   bool no_valid_after = false;
     96   enum GNUNET_DB_QueryStatus qs;
     97   struct GNUNET_JSON_Specification spec[] = {
     98     GNUNET_JSON_spec_string ("slug",
     99                              (const char **) &details.slug),
    100     GNUNET_JSON_spec_string ("name",
    101                              (const char **) &details.name),
    102     GNUNET_JSON_spec_string ("description",
    103                              (const char **) &details.description),
    104     GNUNET_JSON_spec_mark_optional (
    105       GNUNET_JSON_spec_json ("description_i18n",
    106                              &details.description_i18n),
    107       NULL),
    108     GNUNET_JSON_spec_mark_optional (
    109       GNUNET_JSON_spec_json ("extra_data",
    110                              &details.extra_data),
    111       NULL),
    112     GNUNET_JSON_spec_mark_optional (
    113       GNUNET_JSON_spec_timestamp ("valid_after",
    114                                   &details.valid_after),
    115       &no_valid_after),
    116     GNUNET_JSON_spec_timestamp ("valid_before",
    117                                 &details.valid_before),
    118     GNUNET_JSON_spec_relative_time ("duration",
    119                                     &details.duration),
    120     GNUNET_JSON_spec_relative_time ("validity_granularity",
    121                                     &details.validity_granularity),
    122     GNUNET_JSON_spec_mark_optional (
    123       GNUNET_JSON_spec_relative_time ("start_offset",
    124                                       &details.start_offset),
    125       NULL),
    126     GNUNET_JSON_spec_string ("kind",
    127                              &kind),
    128     GNUNET_JSON_spec_end ()
    129   };
    130   struct GNUNET_TIME_Timestamp now
    131     = GNUNET_TIME_timestamp_get ();
    132 
    133   GNUNET_assert (NULL != mi);
    134   {
    135     enum GNUNET_GenericReturnValue res;
    136 
    137     res = TALER_MHD_parse_json_data (connection,
    138                                      hc->request_body,
    139                                      spec);
    140     if (GNUNET_OK != res)
    141     {
    142       GNUNET_break_op (0);
    143       return (GNUNET_NO == res)
    144              ? MHD_YES
    145              : MHD_NO;
    146     }
    147   }
    148   if (no_valid_after)
    149     details.valid_after = now;
    150 
    151   /* Ensure that valid_after is before valid_before */
    152   if (GNUNET_TIME_timestamp_cmp (details.valid_after,
    153                                  >=,
    154                                  details.valid_before))
    155   {
    156     GNUNET_break (0);
    157     GNUNET_JSON_parse_free (spec);
    158     return TALER_MHD_reply_with_error (connection,
    159                                        MHD_HTTP_BAD_REQUEST,
    160                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    161                                        "valid_after >= valid_before");
    162   }
    163 
    164   /* Ensure that duration exceeds rounding plus start_offset */
    165   if (GNUNET_TIME_relative_cmp (details.duration,
    166                                 <,
    167                                 GNUNET_TIME_relative_add (details.
    168                                                           validity_granularity,
    169                                                           details.start_offset))
    170       )
    171   {
    172     GNUNET_break (0);
    173     GNUNET_JSON_parse_free (spec);
    174     return TALER_MHD_reply_with_error (connection,
    175                                        MHD_HTTP_BAD_REQUEST,
    176                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    177                                        "duration below validity_granularity plus start_offset");
    178   }
    179 
    180   if (0 ==
    181       strcmp (kind,
    182               "discount"))
    183     details.kind = TALER_MERCHANTDB_TFK_Discount;
    184   else if (0 ==
    185            strcmp (kind,
    186                    "subscription"))
    187     details.kind = TALER_MERCHANTDB_TFK_Subscription;
    188   else
    189   {
    190     GNUNET_break (0);
    191     GNUNET_JSON_parse_free (spec);
    192     return TALER_MHD_reply_with_error (connection,
    193                                        MHD_HTTP_BAD_REQUEST,
    194                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    195                                        "kind");
    196   }
    197 
    198   if (NULL == details.description_i18n)
    199     details.description_i18n = json_object ();
    200 
    201   if (! TALER_JSON_check_i18n (details.description_i18n))
    202   {
    203     GNUNET_break_op (0);
    204     GNUNET_JSON_parse_free (spec);
    205     return TALER_MHD_reply_with_error (connection,
    206                                        MHD_HTTP_BAD_REQUEST,
    207                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    208                                        "description_i18n");
    209   }
    210 
    211   if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS,
    212                                 !=,
    213                                 details.validity_granularity) &&
    214       GNUNET_TIME_relative_cmp (GNUNET_TIME_relative_multiply (
    215                                   GNUNET_TIME_UNIT_DAYS,
    216                                   90),
    217                                 !=,
    218                                 details.validity_granularity) &&
    219       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS,
    220                                 !=,
    221                                 details.validity_granularity) &&
    222       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_WEEKS,
    223                                 !=,
    224                                 details.validity_granularity) &&
    225       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS,
    226                                 !=,
    227                                 details.validity_granularity) &&
    228       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS,
    229                                 !=,
    230                                 details.validity_granularity) &&
    231       GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES,
    232                                 !=,
    233                                 details.validity_granularity)
    234       )
    235   {
    236     GNUNET_break (0);
    237     GNUNET_JSON_parse_free (spec);
    238     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    239                 "Received invalid validity_granularity value: %s\n",
    240                 GNUNET_STRINGS_relative_time_to_string (details.
    241                                                         validity_granularity,
    242                                                         false));
    243     return TALER_MHD_reply_with_error (connection,
    244                                        MHD_HTTP_BAD_REQUEST,
    245                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    246                                        "validity_granularity");
    247   }
    248 
    249   /* finally, interact with DB until no serialization error */
    250   for (unsigned int i = 0; i<MAX_RETRIES; i++)
    251   {
    252     /* Test if a token family of this id is known */
    253     struct TALER_MERCHANTDB_TokenFamilyDetails existing;
    254 
    255     TALER_MERCHANTDB_preflight (TMH_db);
    256     if (GNUNET_OK !=
    257         TALER_MERCHANTDB_start (TMH_db,
    258                                 "/post tokenfamilies"))
    259     {
    260       GNUNET_break (0);
    261       GNUNET_JSON_parse_free (spec);
    262       return TALER_MHD_reply_with_error (connection,
    263                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    264                                          TALER_EC_GENERIC_DB_START_FAILED,
    265                                          NULL);
    266     }
    267     qs = TALER_MERCHANTDB_insert_token_family (TMH_db,
    268                                                mi->settings.id,
    269                                                details.slug,
    270                                                &details);
    271     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    272                 "insert_token_family returned %d\n",
    273                 (int) qs);
    274     switch (qs)
    275     {
    276     case GNUNET_DB_STATUS_HARD_ERROR:
    277       GNUNET_break (0);
    278       TALER_MERCHANTDB_rollback (TMH_db);
    279       GNUNET_JSON_parse_free (spec);
    280       return TALER_MHD_reply_with_error (
    281         connection,
    282         MHD_HTTP_INTERNAL_SERVER_ERROR,
    283         TALER_EC_GENERIC_DB_STORE_FAILED,
    284         "insert_token_family");
    285     case GNUNET_DB_STATUS_SOFT_ERROR:
    286       GNUNET_break (0);
    287       TALER_MERCHANTDB_rollback (TMH_db);
    288       break;
    289     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    290       qs = TALER_MERCHANTDB_lookup_token_family (TMH_db,
    291                                                  mi->settings.id,
    292                                                  details.slug,
    293                                                  &existing);
    294       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    295                   "lookup_token_family returned %d\n",
    296                   (int) qs);
    297       switch (qs)
    298       {
    299       case GNUNET_DB_STATUS_HARD_ERROR:
    300         /* Clean up and fail hard */
    301         GNUNET_break (0);
    302         TALER_MERCHANTDB_rollback (TMH_db);
    303         GNUNET_JSON_parse_free (spec);
    304         return TALER_MHD_reply_with_error (
    305           connection,
    306           MHD_HTTP_INTERNAL_SERVER_ERROR,
    307           TALER_EC_GENERIC_DB_FETCH_FAILED,
    308           "lookup_token_family");
    309       case GNUNET_DB_STATUS_SOFT_ERROR:
    310         TALER_MERCHANTDB_rollback (TMH_db);
    311         break;
    312       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    313         return TALER_MHD_reply_with_error (
    314           connection,
    315           MHD_HTTP_INTERNAL_SERVER_ERROR,
    316           TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    317           "lookup_token_family failed after insert_token_family failed");
    318       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    319         {
    320           bool eq;
    321 
    322           eq = token_families_equal (&details,
    323                                      &existing);
    324           TALER_MERCHANTDB_token_family_details_free (&existing);
    325           TALER_MERCHANTDB_rollback (TMH_db);
    326           GNUNET_JSON_parse_free (spec);
    327           return eq
    328           ? TALER_MHD_reply_static (
    329             connection,
    330             MHD_HTTP_NO_CONTENT,
    331             NULL,
    332             NULL,
    333             0)
    334           : TALER_MHD_reply_with_error (
    335             connection,
    336             MHD_HTTP_CONFLICT,
    337             TALER_EC_MERCHANT_POST_TOKEN_FAMILY_CONFLICT,
    338             details.slug);
    339         }
    340       }
    341       break;
    342     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    343       qs = TALER_MERCHANTDB_commit (TMH_db);
    344       switch (qs)
    345       {
    346       case GNUNET_DB_STATUS_HARD_ERROR:
    347         /* Clean up and fail hard */
    348         GNUNET_break (0);
    349         TALER_MERCHANTDB_rollback (TMH_db);
    350         GNUNET_JSON_parse_free (spec);
    351         return TALER_MHD_reply_with_error (
    352           connection,
    353           MHD_HTTP_INTERNAL_SERVER_ERROR,
    354           TALER_EC_GENERIC_DB_COMMIT_FAILED,
    355           "insert_token_family");
    356       case GNUNET_DB_STATUS_SOFT_ERROR:
    357         break;
    358       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    359       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    360         break;
    361       }
    362       break;
    363     }
    364     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    365       TALER_MERCHANTDB_rollback (TMH_db);
    366     else
    367       break;
    368   } /* for(i... MAX_RETRIES) */
    369 
    370   GNUNET_JSON_parse_free (spec);
    371   if (qs < 0)
    372   {
    373     GNUNET_break (0);
    374     return TALER_MHD_reply_with_error (
    375       connection,
    376       MHD_HTTP_INTERNAL_SERVER_ERROR,
    377       TALER_EC_GENERIC_DB_SOFT_FAILURE,
    378       NULL);
    379   }
    380   return TALER_MHD_reply_static (connection,
    381                                  MHD_HTTP_NO_CONTENT,
    382                                  NULL,
    383                                  NULL,
    384                                  0);
    385 }
    386 
    387 
    388 /* end of taler-merchant-httpd_post-private-tokenfamilies.c */