merchant

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

taler-merchant-httpd_private-post-token-families.c (14221B)


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