merchant

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

merchant_api_get-private-statistics-amount-SLUG.c (15693B)


      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 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 src/lib/merchant_api_get-private-statistics-amount-SLUG.c
     19  * @brief Implementation of the GET /private/statistics-amount/$SLUG request
     20  * @author Christian Grothoff
     21  */
     22 #include "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/merchant/get-private-statistics-amount-SLUG.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include <taler/taler_json_lib.h>
     31 
     32 
     33 /**
     34  * Maximum number of statistics entries we return.
     35  */
     36 #define MAX_STATISTICS 1024
     37 
     38 
     39 /**
     40  * Handle for a GET /private/statistics-amount/$SLUG operation.
     41  */
     42 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle
     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_GetPrivateStatisticsAmountCallback cb;
     63 
     64   /**
     65    * Closure for @a cb.
     66    */
     67   TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls;
     68 
     69   /**
     70    * Reference to the execution context.
     71    */
     72   struct GNUNET_CURL_Context *ctx;
     73 
     74   /**
     75    * Statistics slug.
     76    */
     77   char *slug;
     78 
     79   /**
     80    * Aggregation mode.
     81    */
     82   enum TALER_MERCHANT_StatisticsType stype;
     83 
     84   /**
     85    * Whether stype was explicitly set.
     86    */
     87   bool have_stype;
     88 };
     89 
     90 
     91 /**
     92  * Parse interval and bucket data from the JSON response.
     93  *
     94  * @param json overall JSON reply
     95  * @param jbuckets JSON array with bucket data
     96  * @param buckets_description human-readable description for buckets
     97  * @param jintervals JSON array with interval data
     98  * @param intervals_description human-readable description for intervals
     99  * @param sah operation handle
    100  * @return #GNUNET_OK on success
    101  */
    102 static enum GNUNET_GenericReturnValue
    103 parse_intervals_and_buckets_amt (
    104   const json_t *json,
    105   const json_t *jbuckets,
    106   const char *buckets_description,
    107   const json_t *jintervals,
    108   const char *intervals_description,
    109   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah)
    110 {
    111   unsigned int resp_buckets_len = json_array_size (jbuckets);
    112   unsigned int resp_intervals_len = json_array_size (jintervals);
    113   size_t total_amounts = 0;
    114   size_t amounts_pos = 0;
    115 
    116   if ( (json_array_size (jbuckets) != (size_t) resp_buckets_len) ||
    117        (json_array_size (jintervals) != (size_t) resp_intervals_len) ||
    118        (resp_buckets_len > MAX_STATISTICS) ||
    119        (resp_intervals_len > MAX_STATISTICS) )
    120   {
    121     GNUNET_break (0);
    122     return GNUNET_SYSERR;
    123   }
    124   {
    125     size_t index;
    126     json_t *value;
    127 
    128     json_array_foreach (jintervals, index, value) {
    129       const json_t *amounts_arr;
    130       struct GNUNET_JSON_Specification spec[] = {
    131         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    132                                       &amounts_arr),
    133         GNUNET_JSON_spec_end ()
    134       };
    135 
    136       if (GNUNET_OK !=
    137           GNUNET_JSON_parse (value,
    138                              spec,
    139                              NULL, NULL))
    140       {
    141         GNUNET_break_op (0);
    142         return GNUNET_SYSERR;
    143       }
    144       total_amounts += json_array_size (amounts_arr);
    145     }
    146 
    147     json_array_foreach (jbuckets, index, value) {
    148       const json_t *amounts_arr;
    149       struct GNUNET_JSON_Specification spec[] = {
    150         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    151                                       &amounts_arr),
    152         GNUNET_JSON_spec_end ()
    153       };
    154 
    155       if (GNUNET_OK !=
    156           GNUNET_JSON_parse (value,
    157                              spec,
    158                              NULL, NULL))
    159       {
    160         GNUNET_break_op (0);
    161         return GNUNET_SYSERR;
    162       }
    163       total_amounts += json_array_size (amounts_arr);
    164     }
    165     if (total_amounts > MAX_STATISTICS * (size_t) MAX_STATISTICS)
    166     {
    167       /* Bound the stack VLA declared below by an untrusted response. */
    168       GNUNET_break_op (0);
    169       return GNUNET_SYSERR;
    170     }
    171   }
    172   {
    173     struct TALER_Amount *glob_amt_arr
    174       = GNUNET_new_array (GNUNET_NZL (total_amounts),
    175                           struct TALER_Amount);
    176     struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket resp_buckets[
    177       GNUNET_NZL (resp_buckets_len)];
    178     struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval resp_intervals[
    179       GNUNET_NZL (resp_intervals_len)];
    180     size_t index;
    181     json_t *value;
    182     enum GNUNET_GenericReturnValue ret;
    183 
    184     ret = GNUNET_OK;
    185     json_array_foreach (jintervals, index, value) {
    186       struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval *jinterval
    187         = &resp_intervals[index];
    188       const json_t *amounts_arr;
    189       size_t amounts_len;
    190       struct GNUNET_JSON_Specification spec[] = {
    191         GNUNET_JSON_spec_timestamp ("start_time",
    192                                     &jinterval->start_time),
    193         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    194                                       &amounts_arr),
    195         GNUNET_JSON_spec_end ()
    196       };
    197 
    198       if (GNUNET_OK !=
    199           GNUNET_JSON_parse (value,
    200                              spec,
    201                              NULL, NULL))
    202       {
    203         GNUNET_break_op (0);
    204         GNUNET_free (glob_amt_arr);
    205         return GNUNET_SYSERR;
    206       }
    207       amounts_len = json_array_size (amounts_arr);
    208       {
    209         struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos];
    210         size_t aindex;
    211         json_t *avalue;
    212 
    213         amounts_pos += amounts_len;
    214         jinterval->cumulative_amount_len = amounts_len;
    215         jinterval->cumulative_amounts = amt_arr;
    216         json_array_foreach (amounts_arr, aindex, avalue) {
    217           if (! json_is_string (avalue))
    218           {
    219             GNUNET_break_op (0);
    220             GNUNET_free (glob_amt_arr);
    221             return GNUNET_SYSERR;
    222           }
    223           if (GNUNET_OK !=
    224               TALER_string_to_amount (json_string_value (avalue),
    225                                       &amt_arr[aindex]))
    226           {
    227             GNUNET_break_op (0);
    228             GNUNET_free (glob_amt_arr);
    229             return GNUNET_SYSERR;
    230           }
    231         }
    232       }
    233     }
    234     json_array_foreach (jbuckets, index, value) {
    235       struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket *jbucket
    236         = &resp_buckets[index];
    237       const json_t *amounts_arr;
    238       size_t amounts_len;
    239       struct GNUNET_JSON_Specification spec[] = {
    240         GNUNET_JSON_spec_timestamp ("start_time",
    241                                     &jbucket->start_time),
    242         GNUNET_JSON_spec_timestamp ("end_time",
    243                                     &jbucket->end_time),
    244         GNUNET_JSON_spec_string ("range",
    245                                  &jbucket->range),
    246         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    247                                       &amounts_arr),
    248         GNUNET_JSON_spec_end ()
    249       };
    250 
    251       if (GNUNET_OK !=
    252           GNUNET_JSON_parse (value,
    253                              spec,
    254                              NULL, NULL))
    255       {
    256         GNUNET_break_op (0);
    257         ret = GNUNET_SYSERR;
    258         break;
    259       }
    260       amounts_len = json_array_size (amounts_arr);
    261       {
    262         struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos];
    263         size_t aindex;
    264         json_t *avalue;
    265 
    266         amounts_pos += amounts_len;
    267         jbucket->cumulative_amount_len = amounts_len;
    268         jbucket->cumulative_amounts = amt_arr;
    269         json_array_foreach (amounts_arr, aindex, avalue) {
    270           if (! json_is_string (avalue))
    271           {
    272             GNUNET_break_op (0);
    273             GNUNET_free (glob_amt_arr);
    274             return GNUNET_SYSERR;
    275           }
    276           if (GNUNET_OK !=
    277               TALER_string_to_amount (json_string_value (avalue),
    278                                       &amt_arr[aindex]))
    279           {
    280             GNUNET_break_op (0);
    281             GNUNET_free (glob_amt_arr);
    282             return GNUNET_SYSERR;
    283           }
    284         }
    285       }
    286     }
    287     if (GNUNET_OK == ret)
    288     {
    289       struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = {
    290         .hr.http_status = MHD_HTTP_OK,
    291         .hr.reply = json,
    292         .details.ok.buckets_length = resp_buckets_len,
    293         .details.ok.buckets = resp_buckets,
    294         .details.ok.buckets_description = buckets_description,
    295         .details.ok.intervals_length = resp_intervals_len,
    296         .details.ok.intervals = resp_intervals,
    297         .details.ok.intervals_description = intervals_description,
    298       };
    299 
    300       sah->cb (sah->cb_cls,
    301                &sagr);
    302       sah->cb = NULL;
    303     }
    304     GNUNET_free (glob_amt_arr);
    305     return ret;
    306   }
    307 }
    308 
    309 
    310 /**
    311  * Function called when we're done processing the
    312  * HTTP GET /private/statistics-amount/$SLUG request.
    313  *
    314  * @param cls the `struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle`
    315  * @param response_code HTTP response code, 0 on error
    316  * @param response response body, NULL if not in JSON
    317  */
    318 static void
    319 handle_get_statistics_amount_finished (void *cls,
    320                                        long response_code,
    321                                        const void *response)
    322 {
    323   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah = cls;
    324   const json_t *json = response;
    325   struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = {
    326     .hr.http_status = (unsigned int) response_code,
    327     .hr.reply = json
    328   };
    329 
    330   sah->job = NULL;
    331   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    332               "Got /private/statistics-amount/$SLUG response with status code %u\n",
    333               (unsigned int) response_code);
    334   switch (response_code)
    335   {
    336   case MHD_HTTP_OK:
    337     {
    338       const json_t *buckets;
    339       const json_t *intervals;
    340       const char *buckets_description = NULL;
    341       const char *intervals_description = NULL;
    342       struct GNUNET_JSON_Specification spec[] = {
    343         GNUNET_JSON_spec_array_const ("buckets",
    344                                       &buckets),
    345         GNUNET_JSON_spec_mark_optional (
    346           GNUNET_JSON_spec_string ("buckets_description",
    347                                    &buckets_description),
    348           NULL),
    349         GNUNET_JSON_spec_array_const ("intervals",
    350                                       &intervals),
    351         GNUNET_JSON_spec_mark_optional (
    352           GNUNET_JSON_spec_string ("intervals_description",
    353                                    &intervals_description),
    354           NULL),
    355         GNUNET_JSON_spec_end ()
    356       };
    357 
    358       if (GNUNET_OK !=
    359           GNUNET_JSON_parse (json,
    360                              spec,
    361                              NULL, NULL))
    362       {
    363         sagr.hr.http_status = 0;
    364         sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    365         break;
    366       }
    367       if (GNUNET_OK ==
    368           parse_intervals_and_buckets_amt (json,
    369                                            buckets,
    370                                            buckets_description,
    371                                            intervals,
    372                                            intervals_description,
    373                                            sah))
    374       {
    375         TALER_MERCHANT_get_private_statistics_amount_cancel (sah);
    376         return;
    377       }
    378       sagr.hr.http_status = 0;
    379       sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    380       break;
    381     }
    382   case MHD_HTTP_UNAUTHORIZED:
    383     sagr.hr.ec = TALER_JSON_get_error_code (json);
    384     sagr.hr.hint = TALER_JSON_get_error_hint (json);
    385     break;
    386   case MHD_HTTP_NOT_FOUND:
    387     sagr.hr.ec = TALER_JSON_get_error_code (json);
    388     sagr.hr.hint = TALER_JSON_get_error_hint (json);
    389     break;
    390   default:
    391     sagr.hr.ec = TALER_JSON_get_error_code (json);
    392     sagr.hr.hint = TALER_JSON_get_error_hint (json);
    393     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    394                 "Unexpected response code %u/%d\n",
    395                 (unsigned int) response_code,
    396                 (int) sagr.hr.ec);
    397     break;
    398   }
    399   sah->cb (sah->cb_cls,
    400            &sagr);
    401   TALER_MERCHANT_get_private_statistics_amount_cancel (sah);
    402 }
    403 
    404 
    405 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *
    406 TALER_MERCHANT_get_private_statistics_amount_create (
    407   struct GNUNET_CURL_Context *ctx,
    408   const char *url,
    409   const char *slug)
    410 {
    411   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah;
    412 
    413   sah = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle);
    414   sah->ctx = ctx;
    415   sah->base_url = GNUNET_strdup (url);
    416   sah->slug = GNUNET_strdup (slug);
    417   sah->stype = TALER_MERCHANT_STATISTICS_ALL;
    418   return sah;
    419 }
    420 
    421 
    422 enum GNUNET_GenericReturnValue
    423 TALER_MERCHANT_get_private_statistics_amount_set_options_ (
    424   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah,
    425   unsigned int num_options,
    426   const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *options)
    427 {
    428   for (unsigned int i = 0; i < num_options; i++)
    429   {
    430     const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *opt =
    431       &options[i];
    432 
    433     switch (opt->option)
    434     {
    435     case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_END:
    436       return GNUNET_OK;
    437     case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_TYPE:
    438       sah->stype = opt->details.type;
    439       sah->have_stype = true;
    440       break;
    441     default:
    442       GNUNET_break (0);
    443       return GNUNET_NO;
    444     }
    445   }
    446   return GNUNET_OK;
    447 }
    448 
    449 
    450 enum TALER_ErrorCode
    451 TALER_MERCHANT_get_private_statistics_amount_start (
    452   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah,
    453   TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb,
    454   TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls)
    455 {
    456   CURL *eh;
    457 
    458   sah->cb = cb;
    459   sah->cb_cls = cb_cls;
    460   {
    461     const char *filter = NULL;
    462     char *path;
    463 
    464     switch (sah->stype)
    465     {
    466     case TALER_MERCHANT_STATISTICS_BY_BUCKET:
    467       filter = "bucket";
    468       break;
    469     case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
    470       filter = "interval";
    471       break;
    472     case TALER_MERCHANT_STATISTICS_ALL:
    473       filter = NULL;
    474       break;
    475     }
    476     GNUNET_asprintf (&path,
    477                      "private/statistics-amount/%s",
    478                      sah->slug);
    479     sah->url = TALER_url_join (sah->base_url,
    480                                path,
    481                                "by",
    482                                filter,
    483                                NULL);
    484     GNUNET_free (path);
    485   }
    486   if (NULL == sah->url)
    487     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    488   eh = TALER_MERCHANT_curl_easy_get_ (sah->url);
    489   if (NULL == eh)
    490     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    491   sah->job = GNUNET_CURL_job_add (sah->ctx,
    492                                   eh,
    493                                   &handle_get_statistics_amount_finished,
    494                                   sah);
    495   if (NULL == sah->job)
    496     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    497   return TALER_EC_NONE;
    498 }
    499 
    500 
    501 void
    502 TALER_MERCHANT_get_private_statistics_amount_cancel (
    503   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah)
    504 {
    505   if (NULL != sah->job)
    506   {
    507     GNUNET_CURL_job_cancel (sah->job);
    508     sah->job = NULL;
    509   }
    510   GNUNET_free (sah->url);
    511   GNUNET_free (sah->base_url);
    512   GNUNET_free (sah->slug);
    513   GNUNET_free (sah);
    514 }
    515 
    516 
    517 /* end of merchant_api_get-private-statistics-amount-SLUG-new.c */