merchant

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

taler-merchant-httpd_private-patch-instances-ID.c (16693B)


      1 /*
      2   This file is part of TALER
      3   (C) 2020-2025 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-patch-instances-ID.c
     22  * @brief implementing PATCH /instances/$ID request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_private-patch-instances-ID.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include <taler/taler_json_lib.h>
     29 #include <taler/taler_dbevents.h>
     30 #include "taler-merchant-httpd_mfa.h"
     31 
     32 
     33 /**
     34  * How often do we retry the simple INSERT database transaction?
     35  */
     36 #define MAX_RETRIES 3
     37 
     38 
     39 /**
     40  * Free memory used by @a wm
     41  *
     42  * @param wm wire method to free
     43  */
     44 static void
     45 free_wm (struct TMH_WireMethod *wm)
     46 {
     47   GNUNET_free (wm->payto_uri.full_payto);
     48   GNUNET_free (wm->wire_method);
     49   GNUNET_free (wm);
     50 }
     51 
     52 
     53 /**
     54  * PATCH configuration of an existing instance, given its configuration.
     55  *
     56  * @param mi instance to patch
     57  * @param connection the MHD connection to handle
     58  * @param[in,out] hc context with further information about the request
     59  * @return MHD result code
     60  */
     61 static MHD_RESULT
     62 patch_instances_ID (struct TMH_MerchantInstance *mi,
     63                     struct MHD_Connection *connection,
     64                     struct TMH_HandlerContext *hc)
     65 {
     66   struct TALER_MERCHANTDB_InstanceSettings is;
     67   const char *name;
     68   struct TMH_WireMethod *wm_head = NULL;
     69   struct TMH_WireMethod *wm_tail = NULL;
     70   bool no_transfer_delay;
     71   bool no_pay_delay;
     72   bool no_refund_delay;
     73   struct GNUNET_JSON_Specification spec[] = {
     74     GNUNET_JSON_spec_string ("name",
     75                              &name),
     76     GNUNET_JSON_spec_mark_optional (
     77       GNUNET_JSON_spec_string ("website",
     78                                (const char **) &is.website),
     79       NULL),
     80     GNUNET_JSON_spec_mark_optional (
     81       GNUNET_JSON_spec_string ("email",
     82                                (const char **) &is.email),
     83       NULL),
     84     GNUNET_JSON_spec_mark_optional (
     85       GNUNET_JSON_spec_string ("phone_number",
     86                                (const char **) &is.phone),
     87       NULL),
     88     GNUNET_JSON_spec_mark_optional (
     89       GNUNET_JSON_spec_string ("logo",
     90                                (const char **) &is.logo),
     91       NULL),
     92     GNUNET_JSON_spec_json ("address",
     93                            &is.address),
     94     GNUNET_JSON_spec_json ("jurisdiction",
     95                            &is.jurisdiction),
     96     GNUNET_JSON_spec_bool ("use_stefan",
     97                            &is.use_stefan),
     98     GNUNET_JSON_spec_mark_optional (
     99       GNUNET_JSON_spec_relative_time ("default_pay_delay",
    100                                       &is.default_pay_delay),
    101       &no_pay_delay),
    102     GNUNET_JSON_spec_mark_optional (
    103       GNUNET_JSON_spec_relative_time ("default_refund_delay",
    104                                       &is.default_refund_delay),
    105       &no_refund_delay),
    106     GNUNET_JSON_spec_mark_optional (
    107       GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
    108                                       &is.default_wire_transfer_delay),
    109       &no_transfer_delay),
    110     GNUNET_JSON_spec_mark_optional (
    111       GNUNET_JSON_spec_time_rounder_interval (
    112         "default_wire_transfer_rounding_interval",
    113         &is.default_wire_transfer_rounding_interval),
    114       NULL),
    115     GNUNET_JSON_spec_end ()
    116   };
    117   enum GNUNET_DB_QueryStatus qs;
    118 
    119   GNUNET_assert (NULL != mi);
    120   memset (&is,
    121           0,
    122           sizeof (is));
    123   {
    124     enum GNUNET_GenericReturnValue res;
    125 
    126     res = TALER_MHD_parse_json_data (connection,
    127                                      hc->request_body,
    128                                      spec);
    129     if (GNUNET_OK != res)
    130       return (GNUNET_NO == res)
    131              ? MHD_YES
    132              : MHD_NO;
    133   }
    134   if (! TMH_location_object_valid (is.address))
    135   {
    136     GNUNET_break_op (0);
    137     GNUNET_JSON_parse_free (spec);
    138     return TALER_MHD_reply_with_error (connection,
    139                                        MHD_HTTP_BAD_REQUEST,
    140                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    141                                        "address");
    142   }
    143   if ( (NULL != is.logo) &&
    144        (! TMH_image_data_url_valid (is.logo)) )
    145   {
    146     GNUNET_break_op (0);
    147     GNUNET_JSON_parse_free (spec);
    148     return TALER_MHD_reply_with_error (connection,
    149                                        MHD_HTTP_BAD_REQUEST,
    150                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    151                                        "logo");
    152   }
    153 
    154   if (! TMH_location_object_valid (is.jurisdiction))
    155   {
    156     GNUNET_break_op (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                                        "jurisdiction");
    162   }
    163 
    164   if (no_transfer_delay)
    165     is.default_wire_transfer_delay = mi->settings.default_wire_transfer_delay;
    166   if (no_pay_delay)
    167     is.default_pay_delay = mi->settings.default_pay_delay;
    168   if (no_refund_delay)
    169     is.default_refund_delay = mi->settings.default_refund_delay;
    170   if (GNUNET_TIME_relative_is_forever (is.default_pay_delay))
    171   {
    172     GNUNET_break_op (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                                        "default_pay_delay");
    178   }
    179   if (GNUNET_TIME_relative_is_forever (is.default_refund_delay))
    180   {
    181     GNUNET_break_op (0);
    182     GNUNET_JSON_parse_free (spec);
    183     return TALER_MHD_reply_with_error (connection,
    184                                        MHD_HTTP_BAD_REQUEST,
    185                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    186                                        "default_refund_delay");
    187   }
    188   if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay))
    189   {
    190     GNUNET_break_op (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                                        "default_wire_transfer_delay");
    196   }
    197 
    198   if ( (NULL != is.phone) &&
    199        (NULL != mi->settings.phone) &&
    200        0 == strcmp (mi->settings.phone,
    201                     is.phone) )
    202     is.phone_validated = mi->settings.phone_validated;
    203   if ( (NULL != is.email) &&
    204        (NULL != mi->settings.email) &&
    205        0 == strcmp (mi->settings.email,
    206                     is.email) )
    207     is.email_validated = mi->settings.email_validated;
    208   {
    209     enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    210     enum TEH_TanChannelSet mtc = TEH_mandatory_tan_channels;
    211 
    212     if ( (0 != (mtc & TEH_TCS_SMS)) &&
    213          (NULL == is.phone) )
    214     {
    215       GNUNET_break_op (0);
    216       GNUNET_JSON_parse_free (spec);
    217       return TALER_MHD_reply_with_error (
    218         connection,
    219         MHD_HTTP_BAD_REQUEST,
    220         TALER_EC_GENERIC_PARAMETER_MISSING,
    221         "phone_number");
    222     }
    223     if ( (0 != (mtc & TEH_TCS_EMAIL)) &&
    224          (NULL == is.email) )
    225     {
    226       GNUNET_break_op (0);
    227       GNUNET_JSON_parse_free (spec);
    228       return TALER_MHD_reply_with_error (
    229         connection,
    230         MHD_HTTP_BAD_REQUEST,
    231         TALER_EC_GENERIC_PARAMETER_MISSING,
    232         "email");
    233     }
    234     if ( (is.phone_validated) &&
    235          (0 != (mtc & TEH_TCS_SMS)) )
    236       mtc -= TEH_TCS_SMS;
    237     if ( (is.email_validated) &&
    238          (0 != (mtc & TEH_TCS_EMAIL)) )
    239       mtc -= TEH_TCS_EMAIL;
    240     switch (mtc)
    241     {
    242     case TEH_TCS_NONE:
    243       ret = GNUNET_OK;
    244       break;
    245     case TEH_TCS_SMS:
    246       if (NULL == is.phone)
    247       {
    248         GNUNET_break_op (0);
    249         GNUNET_JSON_parse_free (spec);
    250         return TALER_MHD_reply_with_error (
    251           connection,
    252           MHD_HTTP_BAD_REQUEST,
    253           TALER_EC_GENERIC_PARAMETER_MISSING,
    254           "phone_number");
    255       }
    256       is.phone_validated = true;
    257       /* validate new phone number, if possible require old e-mail
    258          address for authorization */
    259       ret = TMH_mfa_challenges_do (hc,
    260                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    261                                    true,
    262                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    263                                    is.phone,
    264                                    0 == (TALER_MERCHANT_MFA_CHANNEL_EMAIL
    265                                          & TEH_mandatory_tan_channels)
    266                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
    267                                    : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    268                                    mi->settings.email,
    269                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    270       break;
    271     case TEH_TCS_EMAIL:
    272       is.email_validated = true;
    273       /* validate new e-mail address, if possible require old phone
    274          address for authorization */
    275       ret = TMH_mfa_challenges_do (hc,
    276                                    TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
    277                                    true,
    278                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    279                                    is.email,
    280                                    0 == (TALER_MERCHANT_MFA_CHANNEL_SMS
    281                                          & TEH_mandatory_tan_channels)
    282                                    ? TALER_MERCHANT_MFA_CHANNEL_NONE
    283                                    : TALER_MERCHANT_MFA_CHANNEL_SMS,
    284                                    mi->settings.phone,
    285                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    286       break;
    287     case TEH_TCS_EMAIL_AND_SMS:
    288       is.phone_validated = true;
    289       is.email_validated = true;
    290       /* To change both, we require both old and both new
    291          addresses to consent */
    292       ret = TMH_mfa_challenges_do (hc,
    293                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    294                                    true,
    295                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    296                                    mi->settings.phone,
    297                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    298                                    mi->settings.email,
    299                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    300                                    is.phone,
    301                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    302                                    is.email,
    303                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    304       break;
    305     }
    306     if (GNUNET_OK != ret)
    307     {
    308       GNUNET_JSON_parse_free (spec);
    309       return (GNUNET_NO == ret)
    310         ? MHD_YES
    311         : MHD_NO;
    312     }
    313 
    314   }
    315 
    316   for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
    317   {
    318     /* Cleanup after earlier loops */
    319     {
    320       struct TMH_WireMethod *wm;
    321 
    322       while (NULL != (wm = wm_head))
    323       {
    324         GNUNET_CONTAINER_DLL_remove (wm_head,
    325                                      wm_tail,
    326                                      wm);
    327         free_wm (wm);
    328       }
    329     }
    330     if (GNUNET_OK !=
    331         TMH_db->start (TMH_db->cls,
    332                        "PATCH /instances"))
    333     {
    334       GNUNET_JSON_parse_free (spec);
    335       return TALER_MHD_reply_with_error (connection,
    336                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    337                                          TALER_EC_GENERIC_DB_START_FAILED,
    338                                          NULL);
    339     }
    340     /* Check for equality of settings */
    341     if (! ( (0 == strcmp (mi->settings.name,
    342                           name)) &&
    343             ((mi->settings.email == is.email) ||
    344              (NULL != is.email && NULL != mi->settings.email &&
    345               0 == strcmp (mi->settings.email,
    346                            is.email))) &&
    347             ((mi->settings.phone == is.phone) ||
    348              (NULL != is.phone && NULL != mi->settings.phone &&
    349               0 == strcmp (mi->settings.phone,
    350                            is.phone))) &&
    351             ((mi->settings.website == is.website) ||
    352              (NULL != is.website && NULL != mi->settings.website &&
    353               0 == strcmp (mi->settings.website,
    354                            is.website))) &&
    355             ((mi->settings.logo == is.logo) ||
    356              (NULL != is.logo && NULL != mi->settings.logo &&
    357               0 == strcmp (mi->settings.logo,
    358                            is.logo))) &&
    359             (1 == json_equal (mi->settings.address,
    360                               is.address)) &&
    361             (1 == json_equal (mi->settings.jurisdiction,
    362                               is.jurisdiction)) &&
    363             (mi->settings.use_stefan == is.use_stefan) &&
    364             (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
    365                                        ==,
    366                                        is.default_wire_transfer_delay)) &&
    367             (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
    368                                        ==,
    369                                        is.default_pay_delay)) ) )
    370     {
    371       is.id = mi->settings.id;
    372       is.name = GNUNET_strdup (name);
    373       qs = TMH_db->update_instance (TMH_db->cls,
    374                                     &is);
    375       GNUNET_free (is.name);
    376       if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    377       {
    378         TMH_db->rollback (TMH_db->cls);
    379         if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    380           goto retry;
    381         else
    382           goto giveup;
    383       }
    384     }
    385     qs = TMH_db->commit (TMH_db->cls);
    386 retry:
    387     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    388       continue;
    389     break;
    390   } /* for(... MAX_RETRIES) */
    391 giveup:
    392   /* Update our 'settings' */
    393   GNUNET_free (mi->settings.name);
    394   GNUNET_free (mi->settings.email);
    395   GNUNET_free (mi->settings.phone);
    396   GNUNET_free (mi->settings.website);
    397   GNUNET_free (mi->settings.logo);
    398   json_decref (mi->settings.address);
    399   json_decref (mi->settings.jurisdiction);
    400   is.id = mi->settings.id;
    401   mi->settings = is;
    402   mi->settings.address = json_incref (mi->settings.address);
    403   mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
    404   mi->settings.name = GNUNET_strdup (name);
    405   if (NULL != is.email)
    406     mi->settings.email = GNUNET_strdup (is.email);
    407   if (NULL != is.phone)
    408     mi->settings.phone = GNUNET_strdup (is.phone);
    409   if (NULL != is.website)
    410     mi->settings.website = GNUNET_strdup (is.website);
    411   if (NULL != is.logo)
    412     mi->settings.logo = GNUNET_strdup (is.logo);
    413 
    414   GNUNET_JSON_parse_free (spec);
    415   TMH_reload_instances (mi->settings.id);
    416   return TALER_MHD_reply_static (connection,
    417                                  MHD_HTTP_NO_CONTENT,
    418                                  NULL,
    419                                  NULL,
    420                                  0);
    421 }
    422 
    423 
    424 MHD_RESULT
    425 TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
    426                                 struct MHD_Connection *connection,
    427                                 struct TMH_HandlerContext *hc)
    428 {
    429   struct TMH_MerchantInstance *mi = hc->instance;
    430 
    431   return patch_instances_ID (mi,
    432                              connection,
    433                              hc);
    434 }
    435 
    436 
    437 MHD_RESULT
    438 TMH_private_patch_instances_default_ID (const struct TMH_RequestHandler *rh,
    439                                         struct MHD_Connection *connection,
    440                                         struct TMH_HandlerContext *hc)
    441 {
    442   struct TMH_MerchantInstance *mi;
    443 
    444   mi = TMH_lookup_instance (hc->infix);
    445   if (NULL == mi)
    446   {
    447     return TALER_MHD_reply_with_error (connection,
    448                                        MHD_HTTP_NOT_FOUND,
    449                                        TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    450                                        hc->infix);
    451   }
    452   if (mi->deleted)
    453   {
    454     return TALER_MHD_reply_with_error (connection,
    455                                        MHD_HTTP_CONFLICT,
    456                                        TALER_EC_MERCHANT_PRIVATE_PATCH_INSTANCES_PURGE_REQUIRED,
    457                                        hc->infix);
    458   }
    459   return patch_instances_ID (mi,
    460                              connection,
    461                              hc);
    462 }
    463 
    464 
    465 /* end of taler-merchant-httpd_private-patch-instances-ID.c */