merchant

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

merchant_api_get-private-statistics-counter-SLUG.c (11041B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 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-statistics-counter-SLUG.c
     19  * @brief Implementation of the GET /private/statistics-counter/$SLUG request of the merchant's HTTP API
     20  * @author Martin Schanzenbach
     21  */
     22 #include "taler/platform.h"
     23 #include <curl/curl.h>
     24 #include <gnunet/gnunet_common.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h> /* just for HTTP status codes */
     28 #include <gnunet/gnunet_util_lib.h>
     29 #include <gnunet/gnunet_curl_lib.h>
     30 #include "taler/taler_merchant_service.h"
     31 #include "merchant_api_curl_defaults.h"
     32 #include <taler/taler_json_lib.h>
     33 #include <taler/taler_signatures.h>
     34 
     35 /**
     36  * Maximum number of statistics we return
     37  */
     38 #define MAX_STATISTICS 1024
     39 
     40 /**
     41  * Handle for a GET /statistics-counter/$SLUG operation.
     42  */
     43 struct TALER_MERCHANT_StatisticsCounterGetHandle
     44 {
     45   /**
     46    * The url for this request.
     47    */
     48   char *url;
     49 
     50   /**
     51    * Handle for the request.
     52    */
     53   struct GNUNET_CURL_Job *job;
     54 
     55   /**
     56    * Function to call with the result.
     57    */
     58   TALER_MERCHANT_StatisticsCounterGetCallback cb;
     59 
     60   /**
     61    * Closure for @a cb.
     62    */
     63   void *cb_cls;
     64 
     65   /**
     66    * Reference to the execution context.
     67    */
     68   struct GNUNET_CURL_Context *ctx;
     69 
     70 };
     71 
     72 
     73 /**
     74  * Parse interval information from @a ia.
     75  *
     76  * @param json overall JSON reply
     77  * @param jbuckets JSON array (or NULL!) with bucket data
     78  * @param buckets_description human-readable description for the buckets
     79  * @param jintervals JSON array (or NULL!) with bucket data
     80  * @param intervals_description human-readable description for the intervals
     81  * @param scgh operation handle
     82  * @return #GNUNET_OK on success
     83  */
     84 static enum GNUNET_GenericReturnValue
     85 parse_intervals_and_buckets (
     86   const json_t *json,
     87   const json_t *jbuckets,
     88   const char *buckets_description,
     89   const json_t *jintervals,
     90   const char *intervals_description,
     91   struct TALER_MERCHANT_StatisticsCounterGetHandle *scgh)
     92 {
     93   unsigned int resp_buckets_len = json_array_size (jbuckets);
     94   unsigned int resp_intervals_len = json_array_size (jintervals);
     95 
     96   if ( (json_array_size (jbuckets) != (size_t)  resp_buckets_len) ||
     97        (json_array_size (jintervals) != (size_t)  resp_intervals_len) ||
     98        (resp_intervals_len = resp_buckets_len > MAX_STATISTICS) )
     99   {
    100     GNUNET_break (0);
    101     return GNUNET_SYSERR;
    102   }
    103   {
    104     struct TALER_MERCHANT_StatisticCounterByBucket resp_buckets[
    105       GNUNET_NZL (resp_buckets_len)];
    106     struct TALER_MERCHANT_StatisticCounterByInterval resp_intervals[
    107       GNUNET_NZL (resp_intervals_len)];
    108     size_t index;
    109     json_t *value;
    110     enum GNUNET_GenericReturnValue ret;
    111 
    112     ret = GNUNET_OK;
    113     json_array_foreach (jintervals, index, value) {
    114       struct TALER_MERCHANT_StatisticCounterByInterval *jinterval
    115         = &resp_intervals[index];
    116       struct GNUNET_JSON_Specification spec[] = {
    117         GNUNET_JSON_spec_timestamp ("start_time",
    118                                     &jinterval->start_time),
    119         GNUNET_JSON_spec_uint64 ("cumulative_counter",
    120                                  &jinterval->cumulative_counter),
    121         GNUNET_JSON_spec_end ()
    122       };
    123 
    124       if (GNUNET_OK !=
    125           GNUNET_JSON_parse (value,
    126                              spec,
    127                              NULL, NULL))
    128       {
    129         GNUNET_break_op (0);
    130         ret = GNUNET_SYSERR;
    131         continue;
    132       }
    133       if (GNUNET_SYSERR == ret)
    134         break;
    135     }
    136     ret = GNUNET_OK;
    137     json_array_foreach (jbuckets, index, value) {
    138       struct TALER_MERCHANT_StatisticCounterByBucket *jbucket = &resp_buckets[
    139         index];
    140       struct GNUNET_JSON_Specification spec[] = {
    141         GNUNET_JSON_spec_timestamp ("start_time",
    142                                     &jbucket->start_time),
    143         GNUNET_JSON_spec_timestamp ("end_time",
    144                                     &jbucket->end_time),
    145         GNUNET_JSON_spec_string ("range",
    146                                  &jbucket->range),
    147         GNUNET_JSON_spec_uint64 ("cumulative_counter",
    148                                  &jbucket->cumulative_counter),
    149         GNUNET_JSON_spec_end ()
    150       };
    151 
    152       if (GNUNET_OK !=
    153           GNUNET_JSON_parse (value,
    154                              spec,
    155                              NULL, NULL))
    156       {
    157         GNUNET_break_op (0);
    158         ret = GNUNET_SYSERR;
    159         continue;
    160       }
    161       if (GNUNET_SYSERR == ret)
    162         break;
    163     }
    164     if (GNUNET_OK == ret)
    165     {
    166       struct TALER_MERCHANT_StatisticsCounterGetResponse gsr = {
    167         .hr.http_status = MHD_HTTP_OK,
    168         .hr.reply = json,
    169         .details.ok.buckets_length = resp_buckets_len,
    170         .details.ok.buckets = resp_buckets,
    171         .details.ok.buckets_description = buckets_description,
    172         .details.ok.intervals_length = resp_intervals_len,
    173         .details.ok.intervals = resp_intervals,
    174         .details.ok.intervals_description = intervals_description,
    175       };
    176       scgh->cb (scgh->cb_cls,
    177                 &gsr);
    178       scgh->cb = NULL; /* just to be sure */
    179     }
    180     return ret;
    181   }
    182 }
    183 
    184 
    185 /**
    186  * Function called when we're done processing the
    187  * HTTP GET /statistics-counter/$SLUG request.
    188  *
    189  * @param cls the `struct TALER_MERCHANT_StatisticsCounterGetHandle`
    190  * @param response_code HTTP response code, 0 on error
    191  * @param response response body, NULL if not in JSON
    192  */
    193 static void
    194 handle_get_statistics_counter_finished (void *cls,
    195                                         long response_code,
    196                                         const void *response)
    197 {
    198   struct TALER_MERCHANT_StatisticsCounterGetHandle *handle = cls;
    199   const json_t *json = response;
    200   struct TALER_MERCHANT_StatisticsCounterGetResponse res = {
    201     .hr.http_status = (unsigned int) response_code,
    202     .hr.reply = json
    203   };
    204 
    205   handle->job = NULL;
    206   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    207               "Got /statistics-counter/$SLUG response with status code %u\n",
    208               (unsigned int) response_code);
    209   switch (response_code)
    210   {
    211   case MHD_HTTP_OK:
    212     {
    213       const json_t *buckets;
    214       const json_t *intervals;
    215       const char *buckets_description;
    216       const char *intervals_description;
    217       struct GNUNET_JSON_Specification spec[] = {
    218         GNUNET_JSON_spec_array_const ("buckets",
    219                                       &buckets),
    220         GNUNET_JSON_spec_mark_optional (
    221           GNUNET_JSON_spec_string ("buckets_description",
    222                                    &buckets_description),
    223           NULL),
    224         GNUNET_JSON_spec_array_const ("intervals",
    225                                       &intervals),
    226         GNUNET_JSON_spec_mark_optional (
    227           GNUNET_JSON_spec_string ("intervals_description",
    228                                    &intervals_description),
    229           NULL),
    230         GNUNET_JSON_spec_end ()
    231       };
    232 
    233       if (GNUNET_OK !=
    234           GNUNET_JSON_parse (json,
    235                              spec,
    236                              NULL, NULL))
    237       {
    238         res.hr.http_status = 0;
    239         res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    240         break;
    241       }
    242       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    243                   "%s\n", json_dumps (json, JSON_INDENT (1)));
    244       if (GNUNET_OK ==
    245           parse_intervals_and_buckets (json,
    246                                        buckets,
    247                                        buckets_description,
    248                                        intervals,
    249                                        intervals_description,
    250                                        handle))
    251       {
    252         TALER_MERCHANT_statistic_counter_get_cancel (handle);
    253         return;
    254       }
    255       res.hr.http_status = 0;
    256       res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    257       break;
    258     }
    259   case MHD_HTTP_UNAUTHORIZED:
    260     res.hr.ec = TALER_JSON_get_error_code (json);
    261     res.hr.hint = TALER_JSON_get_error_hint (json);
    262     /* Nothing really to verify, merchant says we need to authenticate. */
    263     break;
    264   case MHD_HTTP_NOT_FOUND:
    265     res.hr.ec = TALER_JSON_get_error_code (json);
    266     res.hr.hint = TALER_JSON_get_error_hint (json);
    267     break;
    268   default:
    269     /* unexpected response code */
    270     res.hr.ec = TALER_JSON_get_error_code (json);
    271     res.hr.hint = TALER_JSON_get_error_hint (json);
    272     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    273                 "Unexpected response code %u/%d\n",
    274                 (unsigned int) response_code,
    275                 (int) res.hr.ec);
    276     break;
    277   }
    278 }
    279 
    280 
    281 struct TALER_MERCHANT_StatisticsCounterGetHandle *
    282 TALER_MERCHANT_statistic_counter_get (
    283   struct GNUNET_CURL_Context *ctx,
    284   const char *backend_url,
    285   const char *slug,
    286   enum TALER_MERCHANT_StatisticsType stype,
    287   TALER_MERCHANT_StatisticsCounterGetCallback cb,
    288   void *cb_cls)
    289 {
    290   struct TALER_MERCHANT_StatisticsCounterGetHandle *handle;
    291   CURL *eh;
    292 
    293   handle = GNUNET_new (struct TALER_MERCHANT_StatisticsCounterGetHandle);
    294   handle->ctx = ctx;
    295   handle->cb = cb;
    296   handle->cb_cls = cb_cls;
    297   {
    298     const char *filter = NULL;
    299     char *path;
    300 
    301     switch (stype)
    302     {
    303     case TALER_MERCHANT_STATISTICS_BY_BUCKET:
    304       filter = "bucket";
    305       break;
    306     case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
    307       filter = "interval";
    308       break;
    309     case TALER_MERCHANT_STATISTICS_ALL:
    310       filter = NULL;
    311       break;
    312     }
    313     GNUNET_asprintf (&path,
    314                      "private/statistics-counter/%s",
    315                      slug);
    316     handle->url = TALER_url_join (backend_url,
    317                                   path,
    318                                   "by",
    319                                   filter,
    320                                   NULL);
    321     GNUNET_free (path);
    322   }
    323   if (NULL == handle->url)
    324   {
    325     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    326                 "Could not construct request URL.\n");
    327     GNUNET_free (handle);
    328     return NULL;
    329   }
    330   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    331               "Requesting URL '%s'\n",
    332               handle->url);
    333   eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
    334   handle->job = GNUNET_CURL_job_add (ctx,
    335                                      eh,
    336                                      &handle_get_statistics_counter_finished,
    337                                      handle);
    338   return handle;
    339 }
    340 
    341 
    342 void
    343 TALER_MERCHANT_statistic_counter_get_cancel (
    344   struct TALER_MERCHANT_StatisticsCounterGetHandle *handle)
    345 {
    346   if (NULL != handle->job)
    347     GNUNET_CURL_job_cancel (handle->job);
    348   GNUNET_free (handle->url);
    349   GNUNET_free (handle);
    350 }
    351 
    352 
    353 /* end of merchant_api_get-private-statistics-counter-SLUG.c */