merchant

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

merchant_api_patch-private-units-UNIT.c (10909B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Lesser General Public License as published by the Free
      7   Software Foundation; either version 2.1, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
     11   PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
     12 
     13   You should have received a copy of the GNU Lesser General Public License along with
     14   TALER; see the file COPYING.LGPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file merchant_api_patch-private-units-UNIT-new.c
     18  * @brief Implementation of the PATCH /private/units/$UNIT request
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include <curl/curl.h>
     23 #include <jansson.h>
     24 #include <microhttpd.h> /* just for HTTP status codes */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include <taler/taler-merchant/patch-private-units-UNIT.h>
     28 #include "merchant_api_curl_defaults.h"
     29 #include "merchant_api_common.h"
     30 #include <taler/taler_json_lib.h>
     31 #include <taler/taler_curl_lib.h>
     32 
     33 
     34 /**
     35  * Handle for a PATCH /private/units/$UNIT operation.
     36  */
     37 struct TALER_MERCHANT_PatchPrivateUnitHandle
     38 {
     39   /**
     40    * Base URL of the merchant backend.
     41    */
     42   char *base_url;
     43 
     44   /**
     45    * The full URL for this request.
     46    */
     47   char *url;
     48 
     49   /**
     50    * Handle for the request.
     51    */
     52   struct GNUNET_CURL_Job *job;
     53 
     54   /**
     55    * Function to call with the result.
     56    */
     57   TALER_MERCHANT_PatchPrivateUnitCallback cb;
     58 
     59   /**
     60    * Closure for @a cb.
     61    */
     62   TALER_MERCHANT_PATCH_PRIVATE_UNIT_RESULT_CLOSURE *cb_cls;
     63 
     64   /**
     65    * Reference to the execution context.
     66    */
     67   struct GNUNET_CURL_Context *ctx;
     68 
     69   /**
     70    * Minor context that holds body and headers.
     71    */
     72   struct TALER_CURL_PostContext post_ctx;
     73 
     74   /**
     75    * Identifier of the unit to update.
     76    */
     77   char *unit_id;
     78 
     79   /**
     80    * Long name of the unit (optional).
     81    */
     82   const char *unit_name_long;
     83 
     84   /**
     85    * Short name (symbol) of the unit (optional).
     86    */
     87   const char *unit_name_short;
     88 
     89   /**
     90    * Internationalized long names (JSON, optional).
     91    */
     92   const json_t *unit_name_long_i18n;
     93 
     94   /**
     95    * Internationalized short names (JSON, optional).
     96    */
     97   const json_t *unit_name_short_i18n;
     98 
     99   /**
    100    * Whether fractional quantities are allowed.
    101    */
    102   bool unit_allow_fraction;
    103 
    104   /**
    105    * Whether unit_allow_fraction was explicitly set.
    106    */
    107   bool have_unit_allow_fraction;
    108 
    109   /**
    110    * Precision level for fractions.
    111    */
    112   uint32_t unit_precision_level;
    113 
    114   /**
    115    * Whether unit_precision_level was explicitly set.
    116    */
    117   bool have_unit_precision_level;
    118 
    119   /**
    120    * Active status of the unit.
    121    */
    122   bool unit_active;
    123 
    124   /**
    125    * Whether unit_active was explicitly set.
    126    */
    127   bool have_unit_active;
    128 };
    129 
    130 
    131 /**
    132  * Function called when we're done processing the
    133  * HTTP PATCH /private/units/$UNIT request.
    134  *
    135  * @param cls the `struct TALER_MERCHANT_PatchPrivateUnitHandle`
    136  * @param response_code HTTP response code, 0 on error
    137  * @param response response body, NULL if not in JSON
    138  */
    139 static void
    140 handle_patch_unit_finished (void *cls,
    141                             long response_code,
    142                             const void *response)
    143 {
    144   struct TALER_MERCHANT_PatchPrivateUnitHandle *uph = cls;
    145   const json_t *json = response;
    146   struct TALER_MERCHANT_PatchPrivateUnitResponse upr = {
    147     .hr.http_status = (unsigned int) response_code,
    148     .hr.reply = json
    149   };
    150 
    151   uph->job = NULL;
    152   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    153               "PATCH /private/units/$UNIT completed with status %u\n",
    154               (unsigned int) response_code);
    155   switch (response_code)
    156   {
    157   case MHD_HTTP_NO_CONTENT:
    158     break;
    159   case MHD_HTTP_BAD_REQUEST:
    160   case MHD_HTTP_UNAUTHORIZED:
    161   case MHD_HTTP_FORBIDDEN:
    162   case MHD_HTTP_NOT_FOUND:
    163   case MHD_HTTP_CONFLICT:
    164   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    165     upr.hr.ec = TALER_JSON_get_error_code (json);
    166     upr.hr.hint = TALER_JSON_get_error_hint (json);
    167     break;
    168   case 0:
    169     upr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    170     break;
    171   default:
    172     TALER_MERCHANT_parse_error_details_ (json,
    173                                          response_code,
    174                                          &upr.hr);
    175     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    176                 "Unexpected response %u/%d for PATCH /private/units\n",
    177                 (unsigned int) response_code,
    178                 (int) upr.hr.ec);
    179     GNUNET_break_op (0);
    180     break;
    181   }
    182   uph->cb (uph->cb_cls,
    183            &upr);
    184   TALER_MERCHANT_patch_private_unit_cancel (uph);
    185 }
    186 
    187 
    188 struct TALER_MERCHANT_PatchPrivateUnitHandle *
    189 TALER_MERCHANT_patch_private_unit_create (
    190   struct GNUNET_CURL_Context *ctx,
    191   const char *url,
    192   const char *unit_id)
    193 {
    194   struct TALER_MERCHANT_PatchPrivateUnitHandle *uph;
    195 
    196   uph = GNUNET_new (struct TALER_MERCHANT_PatchPrivateUnitHandle);
    197   uph->ctx = ctx;
    198   uph->base_url = GNUNET_strdup (url);
    199   uph->unit_id = GNUNET_strdup (unit_id);
    200   return uph;
    201 }
    202 
    203 
    204 enum GNUNET_GenericReturnValue
    205 TALER_MERCHANT_patch_private_unit_set_options_ (
    206   struct TALER_MERCHANT_PatchPrivateUnitHandle *uph,
    207   unsigned int num_options,
    208   const struct TALER_MERCHANT_PatchPrivateUnitOptionValue *options)
    209 {
    210   for (unsigned int i = 0; i < num_options; i++)
    211   {
    212     switch (options[i].option)
    213     {
    214     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_END:
    215       return GNUNET_OK;
    216     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_LONG:
    217       uph->unit_name_long = options[i].details.unit_name_long;
    218       break;
    219     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_SHORT:
    220       uph->unit_name_short = options[i].details.unit_name_short;
    221       break;
    222     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_LONG_I18N:
    223       uph->unit_name_long_i18n = options[i].details.unit_name_long_i18n;
    224       break;
    225     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_SHORT_I18N:
    226       uph->unit_name_short_i18n = options[i].details.unit_name_short_i18n;
    227       break;
    228     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_ALLOW_FRACTION:
    229       uph->unit_allow_fraction = options[i].details.unit_allow_fraction;
    230       uph->have_unit_allow_fraction = true;
    231       break;
    232     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_PRECISION_LEVEL:
    233       uph->unit_precision_level = options[i].details.unit_precision_level;
    234       uph->have_unit_precision_level = true;
    235       break;
    236     case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_ACTIVE:
    237       uph->unit_active = options[i].details.unit_active;
    238       uph->have_unit_active = true;
    239       break;
    240     default:
    241       GNUNET_break (0);
    242       return GNUNET_SYSERR;
    243     }
    244   }
    245   return GNUNET_OK;
    246 }
    247 
    248 
    249 enum TALER_ErrorCode
    250 TALER_MERCHANT_patch_private_unit_start (
    251   struct TALER_MERCHANT_PatchPrivateUnitHandle *uph,
    252   TALER_MERCHANT_PatchPrivateUnitCallback cb,
    253   TALER_MERCHANT_PATCH_PRIVATE_UNIT_RESULT_CLOSURE *cb_cls)
    254 {
    255   json_t *req_obj;
    256   CURL *eh;
    257 
    258   uph->cb = cb;
    259   uph->cb_cls = cb_cls;
    260   {
    261     char *path;
    262 
    263     GNUNET_asprintf (&path,
    264                      "private/units/%s",
    265                      uph->unit_id);
    266     uph->url = TALER_url_join (uph->base_url,
    267                                path,
    268                                NULL);
    269     GNUNET_free (path);
    270   }
    271   if (NULL == uph->url)
    272     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    273 
    274   /* Build JSON request body from options that were set */
    275   req_obj = json_object ();
    276   if (NULL == req_obj)
    277     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    278   if (NULL != uph->unit_name_long)
    279   {
    280     GNUNET_assert (0 ==
    281                    json_object_set_new (req_obj,
    282                                         "unit_name_long",
    283                                         json_string (uph->unit_name_long)));
    284   }
    285   if (NULL != uph->unit_name_short)
    286   {
    287     GNUNET_assert (0 ==
    288                    json_object_set_new (req_obj,
    289                                         "unit_name_short",
    290                                         json_string (uph->unit_name_short)));
    291   }
    292   if (NULL != uph->unit_name_long_i18n)
    293   {
    294     GNUNET_assert (0 ==
    295                    json_object_set_new (
    296                      req_obj,
    297                      "unit_name_long_i18n",
    298                      json_incref ((json_t *) uph->unit_name_long_i18n)));
    299   }
    300   if (NULL != uph->unit_name_short_i18n)
    301   {
    302     GNUNET_assert (0 ==
    303                    json_object_set_new (
    304                      req_obj,
    305                      "unit_name_short_i18n",
    306                      json_incref ((json_t *) uph->unit_name_short_i18n)));
    307   }
    308   if (uph->have_unit_allow_fraction)
    309   {
    310     GNUNET_assert (0 ==
    311                    json_object_set_new (req_obj,
    312                                         "unit_allow_fraction",
    313                                         json_boolean (
    314                                           uph->unit_allow_fraction)));
    315   }
    316   if (uph->have_unit_precision_level)
    317   {
    318     GNUNET_assert (0 ==
    319                    json_object_set_new (req_obj,
    320                                         "unit_precision_level",
    321                                         json_integer (
    322                                           (json_int_t)
    323                                           uph->unit_precision_level)));
    324   }
    325   if (uph->have_unit_active)
    326   {
    327     GNUNET_assert (0 ==
    328                    json_object_set_new (req_obj,
    329                                         "unit_active",
    330                                         json_boolean (uph->unit_active)));
    331   }
    332   eh = TALER_MERCHANT_curl_easy_get_ (uph->url);
    333   if ( (NULL == eh) ||
    334        (GNUNET_OK !=
    335         TALER_curl_easy_post (&uph->post_ctx,
    336                               eh,
    337                               req_obj)) )
    338   {
    339     GNUNET_break (0);
    340     json_decref (req_obj);
    341     if (NULL != eh)
    342       curl_easy_cleanup (eh);
    343     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    344   }
    345   json_decref (req_obj);
    346   GNUNET_assert (CURLE_OK ==
    347                  curl_easy_setopt (eh,
    348                                    CURLOPT_CUSTOMREQUEST,
    349                                    MHD_HTTP_METHOD_PATCH));
    350   uph->job = GNUNET_CURL_job_add2 (uph->ctx,
    351                                    eh,
    352                                    uph->post_ctx.headers,
    353                                    &handle_patch_unit_finished,
    354                                    uph);
    355   if (NULL == uph->job)
    356     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    357   return TALER_EC_NONE;
    358 }
    359 
    360 
    361 void
    362 TALER_MERCHANT_patch_private_unit_cancel (
    363   struct TALER_MERCHANT_PatchPrivateUnitHandle *uph)
    364 {
    365   if (NULL != uph->job)
    366   {
    367     GNUNET_CURL_job_cancel (uph->job);
    368     uph->job = NULL;
    369   }
    370   TALER_curl_easy_post_finished (&uph->post_ctx);
    371   GNUNET_free (uph->url);
    372   GNUNET_free (uph->base_url);
    373   GNUNET_free (uph->unit_id);
    374   GNUNET_free (uph);
    375 }
    376 
    377 
    378 /* end of merchant_api_patch-private-units-UNIT-new.c */