merchant

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

merchant_api_get-private-products.c (12539B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-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 Software
      7   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
     11   A 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
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file merchant_api_get-private-products.c
     19  * @brief Implementation of the GET /private/products request
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <curl/curl.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include <taler/taler-merchant/get-private-products.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include <taler/taler_json_lib.h>
     31 
     32 
     33 /**
     34  * Maximum number of products we return.
     35  */
     36 #define MAX_PRODUCTS 1024
     37 
     38 
     39 /**
     40  * Handle for a GET /private/products operation.
     41  */
     42 struct TALER_MERCHANT_GetPrivateProductsHandle
     43 {
     44   /**
     45    * Base URL of the merchant backend.
     46    */
     47   char *base_url;
     48 
     49   /**
     50    * The full URL for this request.
     51    */
     52   char *url;
     53 
     54   /**
     55    * Handle for the request.
     56    */
     57   struct GNUNET_CURL_Job *job;
     58 
     59   /**
     60    * Function to call with the result.
     61    */
     62   TALER_MERCHANT_GetPrivateProductsCallback cb;
     63 
     64   /**
     65    * Closure for @a cb.
     66    */
     67   TALER_MERCHANT_GET_PRIVATE_PRODUCTS_RESULT_CLOSURE *cb_cls;
     68 
     69   /**
     70    * Reference to the execution context.
     71    */
     72   struct GNUNET_CURL_Context *ctx;
     73 
     74   /**
     75    * Starting row for pagination.
     76    */
     77   uint64_t offset;
     78 
     79   /**
     80    * Limit on number of results.
     81    */
     82   int64_t limit;
     83 
     84   /**
     85    * Category name filter, or NULL.
     86    */
     87   char *category_filter;
     88 
     89   /**
     90    * Product name filter, or NULL.
     91    */
     92   char *name_filter;
     93 
     94   /**
     95    * Product description filter, or NULL.
     96    */
     97   char *description_filter;
     98 
     99   /**
    100    * Product group serial filter.
    101    */
    102   uint64_t product_group_serial;
    103 
    104   /**
    105    * True if offset was explicitly set.
    106    */
    107   bool have_offset;
    108 
    109   /**
    110    * True if product_group_serial was explicitly set.
    111    */
    112   bool have_product_group_serial;
    113 };
    114 
    115 
    116 /**
    117  * Parse product information from @a ia.
    118  *
    119  * @param ia JSON array (or NULL!) with product data
    120  * @param[in] pgr partially filled response
    121  * @param gpph operation handle
    122  * @return #GNUNET_OK on success
    123  */
    124 static enum GNUNET_GenericReturnValue
    125 parse_products (const json_t *ia,
    126                 struct TALER_MERCHANT_GetPrivateProductsResponse *pgr,
    127                 struct TALER_MERCHANT_GetPrivateProductsHandle *gpph)
    128 {
    129   unsigned int ies_len = (unsigned int) json_array_size (ia);
    130 
    131   if ( (json_array_size (ia) != (size_t) ies_len) ||
    132        (ies_len > MAX_PRODUCTS) )
    133   {
    134     GNUNET_break (0);
    135     return GNUNET_SYSERR;
    136   }
    137   {
    138     struct TALER_MERCHANT_GetPrivateProductsInventoryEntry ies[
    139       GNUNET_NZL (ies_len)];
    140     size_t index;
    141     json_t *value;
    142 
    143     json_array_foreach (ia, index, value) {
    144       struct TALER_MERCHANT_GetPrivateProductsInventoryEntry *ie =
    145         &ies[index];
    146       struct GNUNET_JSON_Specification spec[] = {
    147         GNUNET_JSON_spec_string ("product_id",
    148                                  &ie->product_id),
    149         GNUNET_JSON_spec_uint64 ("product_serial",
    150                                  &ie->product_serial),
    151         GNUNET_JSON_spec_end ()
    152       };
    153 
    154       if (GNUNET_OK !=
    155           GNUNET_JSON_parse (value,
    156                              spec,
    157                              NULL, NULL))
    158       {
    159         GNUNET_break_op (0);
    160         return GNUNET_SYSERR;
    161       }
    162     }
    163     pgr->details.ok.products_length = ies_len;
    164     pgr->details.ok.products = ies;
    165     gpph->cb (gpph->cb_cls,
    166               pgr);
    167     gpph->cb = NULL;
    168   }
    169   return GNUNET_OK;
    170 }
    171 
    172 
    173 /**
    174  * Function called when we're done processing the
    175  * HTTP GET /private/products request.
    176  *
    177  * @param cls the `struct TALER_MERCHANT_GetPrivateProductsHandle`
    178  * @param response_code HTTP response code, 0 on error
    179  * @param response response body, NULL if not in JSON
    180  */
    181 static void
    182 handle_get_products_finished (void *cls,
    183                               long response_code,
    184                               const void *response)
    185 {
    186   struct TALER_MERCHANT_GetPrivateProductsHandle *gpph = cls;
    187   const json_t *json = response;
    188   struct TALER_MERCHANT_GetPrivateProductsResponse pgr = {
    189     .hr.http_status = (unsigned int) response_code,
    190     .hr.reply = json
    191   };
    192 
    193   gpph->job = NULL;
    194   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    195               "Got /private/products response with status code %u\n",
    196               (unsigned int) response_code);
    197   switch (response_code)
    198   {
    199   case MHD_HTTP_OK:
    200     {
    201       const json_t *products;
    202       struct GNUNET_JSON_Specification spec[] = {
    203         GNUNET_JSON_spec_array_const ("products",
    204                                       &products),
    205         GNUNET_JSON_spec_end ()
    206       };
    207 
    208       if (GNUNET_OK !=
    209           GNUNET_JSON_parse (json,
    210                              spec,
    211                              NULL, NULL))
    212       {
    213         pgr.hr.http_status = 0;
    214         pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    215         break;
    216       }
    217       if (GNUNET_OK ==
    218           parse_products (products,
    219                           &pgr,
    220                           gpph))
    221       {
    222         TALER_MERCHANT_get_private_products_cancel (gpph);
    223         return;
    224       }
    225       pgr.hr.http_status = 0;
    226       pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    227       break;
    228     }
    229   case MHD_HTTP_UNAUTHORIZED:
    230     pgr.hr.ec = TALER_JSON_get_error_code (json);
    231     pgr.hr.hint = TALER_JSON_get_error_hint (json);
    232     break;
    233   default:
    234     pgr.hr.ec = TALER_JSON_get_error_code (json);
    235     pgr.hr.hint = TALER_JSON_get_error_hint (json);
    236     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    237                 "Unexpected response code %u/%d\n",
    238                 (unsigned int) response_code,
    239                 (int) pgr.hr.ec);
    240     break;
    241   }
    242   gpph->cb (gpph->cb_cls,
    243             &pgr);
    244   TALER_MERCHANT_get_private_products_cancel (gpph);
    245 }
    246 
    247 
    248 struct TALER_MERCHANT_GetPrivateProductsHandle *
    249 TALER_MERCHANT_get_private_products_create (
    250   struct GNUNET_CURL_Context *ctx,
    251   const char *url)
    252 {
    253   struct TALER_MERCHANT_GetPrivateProductsHandle *gpph;
    254 
    255   gpph = GNUNET_new (struct TALER_MERCHANT_GetPrivateProductsHandle);
    256   gpph->ctx = ctx;
    257   gpph->base_url = GNUNET_strdup (url);
    258   gpph->limit = 20;
    259   gpph->offset = INT64_MAX;
    260   return gpph;
    261 }
    262 
    263 
    264 enum GNUNET_GenericReturnValue
    265 TALER_MERCHANT_get_private_products_set_options_ (
    266   struct TALER_MERCHANT_GetPrivateProductsHandle *gpph,
    267   unsigned int num_options,
    268   const struct TALER_MERCHANT_GetPrivateProductsOptionValue *options)
    269 {
    270   for (unsigned int i = 0; i < num_options; i++)
    271   {
    272     const struct TALER_MERCHANT_GetPrivateProductsOptionValue *opt =
    273       &options[i];
    274 
    275     switch (opt->option)
    276     {
    277     case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_END:
    278       return GNUNET_OK;
    279     case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_LIMIT:
    280       gpph->limit = opt->details.limit;
    281       break;
    282     case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_OFFSET:
    283       if (opt->details.offset > INT64_MAX)
    284       {
    285         GNUNET_break (0);
    286         return GNUNET_NO;
    287       }
    288       gpph->offset = opt->details.offset;
    289       gpph->have_offset = true;
    290       break;
    291     case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_CATEGORY_FILTER:
    292       GNUNET_free (gpph->category_filter);
    293       if (NULL != opt->details.category_filter)
    294         gpph->category_filter = GNUNET_strdup (opt->details.category_filter);
    295       break;
    296     case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_NAME_FILTER:
    297       GNUNET_free (gpph->name_filter);
    298       if (NULL != opt->details.name_filter)
    299         gpph->name_filter = GNUNET_strdup (opt->details.name_filter);
    300       break;
    301     case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_DESCRIPTION_FILTER:
    302       GNUNET_free (gpph->description_filter);
    303       if (NULL != opt->details.description_filter)
    304         gpph->description_filter =
    305           GNUNET_strdup (opt->details.description_filter);
    306       break;
    307     case TALER_MERCHANT_GET_PRIVATE_PRODUCTS_OPTION_PRODUCT_GROUP_SERIAL:
    308       gpph->product_group_serial = opt->details.product_group_serial;
    309       gpph->have_product_group_serial = true;
    310       break;
    311     default:
    312       GNUNET_break (0);
    313       return GNUNET_NO;
    314     }
    315   }
    316   return GNUNET_OK;
    317 }
    318 
    319 
    320 enum TALER_ErrorCode
    321 TALER_MERCHANT_get_private_products_start (
    322   struct TALER_MERCHANT_GetPrivateProductsHandle *gpph,
    323   TALER_MERCHANT_GetPrivateProductsCallback cb,
    324   TALER_MERCHANT_GET_PRIVATE_PRODUCTS_RESULT_CLOSURE *cb_cls)
    325 {
    326   CURL *eh;
    327 
    328   gpph->cb = cb;
    329   gpph->cb_cls = cb_cls;
    330   {
    331     char *cfilt = NULL;
    332     char *nfilt = NULL;
    333     char *dfilt = NULL;
    334     char lbuf[30];
    335     char obuf[30];
    336     char pgsbuf[30];
    337     bool have_offset;
    338 
    339     GNUNET_snprintf (lbuf,
    340                      sizeof (lbuf),
    341                      "%lld",
    342                      (long long) gpph->limit);
    343     GNUNET_snprintf (obuf,
    344                      sizeof (obuf),
    345                      "%llu",
    346                      (unsigned long long) gpph->offset);
    347     if (gpph->have_product_group_serial)
    348       GNUNET_snprintf (pgsbuf,
    349                        sizeof (pgsbuf),
    350                        "%llu",
    351                        (unsigned long long) gpph->product_group_serial);
    352     if (gpph->limit > 0)
    353     {
    354       have_offset = gpph->have_offset
    355                     && (0 != gpph->offset);
    356     }
    357     else
    358     {
    359       have_offset = gpph->have_offset
    360                     && (INT64_MAX != gpph->offset);
    361     }
    362     if (NULL != gpph->category_filter)
    363       (void) GNUNET_STRINGS_urlencode (strlen (gpph->category_filter),
    364                                        gpph->category_filter,
    365                                        &cfilt);
    366     if (NULL != gpph->name_filter)
    367       (void) GNUNET_STRINGS_urlencode (strlen (gpph->name_filter),
    368                                        gpph->name_filter,
    369                                        &nfilt);
    370     if (NULL != gpph->description_filter)
    371       (void) GNUNET_STRINGS_urlencode (strlen (gpph->description_filter),
    372                                        gpph->description_filter,
    373                                        &dfilt);
    374     gpph->url = TALER_url_join (gpph->base_url,
    375                                 "private/products",
    376                                 "limit",
    377                                 (20 != gpph->limit)
    378                                 ? lbuf
    379                                 : NULL,
    380                                 "offset",
    381                                 have_offset
    382                                 ? obuf
    383                                 : NULL,
    384                                 "category_filter",
    385                                 cfilt,
    386                                 "name_filter",
    387                                 nfilt,
    388                                 "description_filter",
    389                                 dfilt,
    390                                 "product_group_serial",
    391                                 gpph->have_product_group_serial
    392                                 ? pgsbuf
    393                                 : NULL,
    394                                 NULL);
    395     GNUNET_free (cfilt);
    396     GNUNET_free (nfilt);
    397     GNUNET_free (dfilt);
    398   }
    399   if (NULL == gpph->url)
    400   {
    401     GNUNET_break (0);
    402     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    403   }
    404   eh = TALER_MERCHANT_curl_easy_get_ (gpph->url);
    405   if (NULL == eh)
    406   {
    407     GNUNET_break (0);
    408     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    409   }
    410   gpph->job = GNUNET_CURL_job_add (gpph->ctx,
    411                                    eh,
    412                                    &handle_get_products_finished,
    413                                    gpph);
    414   if (NULL == gpph->job)
    415   {
    416     GNUNET_break (0);
    417     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    418   }
    419   return TALER_EC_NONE;
    420 }
    421 
    422 
    423 void
    424 TALER_MERCHANT_get_private_products_cancel (
    425   struct TALER_MERCHANT_GetPrivateProductsHandle *gpph)
    426 {
    427   if (NULL != gpph->job)
    428   {
    429     GNUNET_CURL_job_cancel (gpph->job);
    430     gpph->job = NULL;
    431   }
    432   GNUNET_free (gpph->url);
    433   GNUNET_free (gpph->category_filter);
    434   GNUNET_free (gpph->name_filter);
    435   GNUNET_free (gpph->description_filter);
    436   GNUNET_free (gpph->base_url);
    437   GNUNET_free (gpph);
    438 }
    439 
    440 
    441 /* end of merchant_api_get-private-products.c */