merchant

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

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


      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   }
    166   {
    167     struct TALER_Amount glob_amt_arr[GNUNET_NZL (total_amounts)];
    168     struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket resp_buckets[
    169       GNUNET_NZL (resp_buckets_len)];
    170     struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval resp_intervals[
    171       GNUNET_NZL (resp_intervals_len)];
    172     size_t index;
    173     json_t *value;
    174     enum GNUNET_GenericReturnValue ret;
    175 
    176     ret = GNUNET_OK;
    177     json_array_foreach (jintervals, index, value) {
    178       struct TALER_MERCHANT_GetPrivateStatisticsAmountByInterval *jinterval
    179         = &resp_intervals[index];
    180       const json_t *amounts_arr;
    181       size_t amounts_len;
    182       struct GNUNET_JSON_Specification spec[] = {
    183         GNUNET_JSON_spec_timestamp ("start_time",
    184                                     &jinterval->start_time),
    185         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    186                                       &amounts_arr),
    187         GNUNET_JSON_spec_end ()
    188       };
    189 
    190       if (GNUNET_OK !=
    191           GNUNET_JSON_parse (value,
    192                              spec,
    193                              NULL, NULL))
    194       {
    195         GNUNET_break_op (0);
    196         return GNUNET_SYSERR;
    197       }
    198       amounts_len = json_array_size (amounts_arr);
    199       {
    200         struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos];
    201         size_t aindex;
    202         json_t *avalue;
    203 
    204         amounts_pos += amounts_len;
    205         jinterval->cumulative_amount_len = amounts_len;
    206         jinterval->cumulative_amounts = amt_arr;
    207         json_array_foreach (amounts_arr, aindex, avalue) {
    208           if (! json_is_string (avalue))
    209           {
    210             GNUNET_break_op (0);
    211             return GNUNET_SYSERR;
    212           }
    213           if (GNUNET_OK !=
    214               TALER_string_to_amount (json_string_value (avalue),
    215                                       &amt_arr[aindex]))
    216           {
    217             GNUNET_break_op (0);
    218             return GNUNET_SYSERR;
    219           }
    220         }
    221       }
    222     }
    223     json_array_foreach (jbuckets, index, value) {
    224       struct TALER_MERCHANT_GetPrivateStatisticsAmountByBucket *jbucket
    225         = &resp_buckets[index];
    226       const json_t *amounts_arr;
    227       size_t amounts_len;
    228       struct GNUNET_JSON_Specification spec[] = {
    229         GNUNET_JSON_spec_timestamp ("start_time",
    230                                     &jbucket->start_time),
    231         GNUNET_JSON_spec_timestamp ("end_time",
    232                                     &jbucket->end_time),
    233         GNUNET_JSON_spec_string ("range",
    234                                  &jbucket->range),
    235         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    236                                       &amounts_arr),
    237         GNUNET_JSON_spec_end ()
    238       };
    239 
    240       if (GNUNET_OK !=
    241           GNUNET_JSON_parse (value,
    242                              spec,
    243                              NULL, NULL))
    244       {
    245         GNUNET_break_op (0);
    246         ret = GNUNET_SYSERR;
    247         break;
    248       }
    249       amounts_len = json_array_size (amounts_arr);
    250       {
    251         struct TALER_Amount *amt_arr = &glob_amt_arr[amounts_pos];
    252         size_t aindex;
    253         json_t *avalue;
    254 
    255         amounts_pos += amounts_len;
    256         jbucket->cumulative_amount_len = amounts_len;
    257         jbucket->cumulative_amounts = amt_arr;
    258         json_array_foreach (amounts_arr, aindex, avalue) {
    259           if (! json_is_string (avalue))
    260           {
    261             GNUNET_break_op (0);
    262             return GNUNET_SYSERR;
    263           }
    264           if (GNUNET_OK !=
    265               TALER_string_to_amount (json_string_value (avalue),
    266                                       &amt_arr[aindex]))
    267           {
    268             GNUNET_break_op (0);
    269             return GNUNET_SYSERR;
    270           }
    271         }
    272       }
    273     }
    274     if (GNUNET_OK == ret)
    275     {
    276       struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = {
    277         .hr.http_status = MHD_HTTP_OK,
    278         .hr.reply = json,
    279         .details.ok.buckets_length = resp_buckets_len,
    280         .details.ok.buckets = resp_buckets,
    281         .details.ok.buckets_description = buckets_description,
    282         .details.ok.intervals_length = resp_intervals_len,
    283         .details.ok.intervals = resp_intervals,
    284         .details.ok.intervals_description = intervals_description,
    285       };
    286 
    287       sah->cb (sah->cb_cls,
    288                &sagr);
    289       sah->cb = NULL;
    290     }
    291     return ret;
    292   }
    293 }
    294 
    295 
    296 /**
    297  * Function called when we're done processing the
    298  * HTTP GET /private/statistics-amount/$SLUG request.
    299  *
    300  * @param cls the `struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle`
    301  * @param response_code HTTP response code, 0 on error
    302  * @param response response body, NULL if not in JSON
    303  */
    304 static void
    305 handle_get_statistics_amount_finished (void *cls,
    306                                        long response_code,
    307                                        const void *response)
    308 {
    309   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah = cls;
    310   const json_t *json = response;
    311   struct TALER_MERCHANT_GetPrivateStatisticsAmountResponse sagr = {
    312     .hr.http_status = (unsigned int) response_code,
    313     .hr.reply = json
    314   };
    315 
    316   sah->job = NULL;
    317   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    318               "Got /private/statistics-amount/$SLUG response with status code %u\n",
    319               (unsigned int) response_code);
    320   switch (response_code)
    321   {
    322   case MHD_HTTP_OK:
    323     {
    324       const json_t *buckets;
    325       const json_t *intervals;
    326       const char *buckets_description = NULL;
    327       const char *intervals_description = NULL;
    328       struct GNUNET_JSON_Specification spec[] = {
    329         GNUNET_JSON_spec_array_const ("buckets",
    330                                       &buckets),
    331         GNUNET_JSON_spec_mark_optional (
    332           GNUNET_JSON_spec_string ("buckets_description",
    333                                    &buckets_description),
    334           NULL),
    335         GNUNET_JSON_spec_array_const ("intervals",
    336                                       &intervals),
    337         GNUNET_JSON_spec_mark_optional (
    338           GNUNET_JSON_spec_string ("intervals_description",
    339                                    &intervals_description),
    340           NULL),
    341         GNUNET_JSON_spec_end ()
    342       };
    343 
    344       if (GNUNET_OK !=
    345           GNUNET_JSON_parse (json,
    346                              spec,
    347                              NULL, NULL))
    348       {
    349         sagr.hr.http_status = 0;
    350         sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    351         break;
    352       }
    353       if (GNUNET_OK ==
    354           parse_intervals_and_buckets_amt (json,
    355                                            buckets,
    356                                            buckets_description,
    357                                            intervals,
    358                                            intervals_description,
    359                                            sah))
    360       {
    361         TALER_MERCHANT_get_private_statistics_amount_cancel (sah);
    362         return;
    363       }
    364       sagr.hr.http_status = 0;
    365       sagr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    366       break;
    367     }
    368   case MHD_HTTP_UNAUTHORIZED:
    369     sagr.hr.ec = TALER_JSON_get_error_code (json);
    370     sagr.hr.hint = TALER_JSON_get_error_hint (json);
    371     break;
    372   case MHD_HTTP_NOT_FOUND:
    373     sagr.hr.ec = TALER_JSON_get_error_code (json);
    374     sagr.hr.hint = TALER_JSON_get_error_hint (json);
    375     break;
    376   default:
    377     sagr.hr.ec = TALER_JSON_get_error_code (json);
    378     sagr.hr.hint = TALER_JSON_get_error_hint (json);
    379     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    380                 "Unexpected response code %u/%d\n",
    381                 (unsigned int) response_code,
    382                 (int) sagr.hr.ec);
    383     break;
    384   }
    385   sah->cb (sah->cb_cls,
    386            &sagr);
    387   TALER_MERCHANT_get_private_statistics_amount_cancel (sah);
    388 }
    389 
    390 
    391 struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *
    392 TALER_MERCHANT_get_private_statistics_amount_create (
    393   struct GNUNET_CURL_Context *ctx,
    394   const char *url,
    395   const char *slug)
    396 {
    397   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah;
    398 
    399   sah = GNUNET_new (struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle);
    400   sah->ctx = ctx;
    401   sah->base_url = GNUNET_strdup (url);
    402   sah->slug = GNUNET_strdup (slug);
    403   sah->stype = TALER_MERCHANT_STATISTICS_ALL;
    404   return sah;
    405 }
    406 
    407 
    408 enum GNUNET_GenericReturnValue
    409 TALER_MERCHANT_get_private_statistics_amount_set_options_ (
    410   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah,
    411   unsigned int num_options,
    412   const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *options)
    413 {
    414   for (unsigned int i = 0; i < num_options; i++)
    415   {
    416     const struct TALER_MERCHANT_GetPrivateStatisticsAmountOptionValue *opt =
    417       &options[i];
    418 
    419     switch (opt->option)
    420     {
    421     case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_END:
    422       return GNUNET_OK;
    423     case TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_OPTION_TYPE:
    424       sah->stype = opt->details.type;
    425       sah->have_stype = true;
    426       break;
    427     default:
    428       GNUNET_break (0);
    429       return GNUNET_NO;
    430     }
    431   }
    432   return GNUNET_OK;
    433 }
    434 
    435 
    436 enum TALER_ErrorCode
    437 TALER_MERCHANT_get_private_statistics_amount_start (
    438   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah,
    439   TALER_MERCHANT_GetPrivateStatisticsAmountCallback cb,
    440   TALER_MERCHANT_GET_PRIVATE_STATISTICS_AMOUNT_RESULT_CLOSURE *cb_cls)
    441 {
    442   CURL *eh;
    443 
    444   sah->cb = cb;
    445   sah->cb_cls = cb_cls;
    446   {
    447     const char *filter = NULL;
    448     char *path;
    449 
    450     switch (sah->stype)
    451     {
    452     case TALER_MERCHANT_STATISTICS_BY_BUCKET:
    453       filter = "bucket";
    454       break;
    455     case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
    456       filter = "interval";
    457       break;
    458     case TALER_MERCHANT_STATISTICS_ALL:
    459       filter = NULL;
    460       break;
    461     }
    462     GNUNET_asprintf (&path,
    463                      "private/statistics-amount/%s",
    464                      sah->slug);
    465     sah->url = TALER_url_join (sah->base_url,
    466                                path,
    467                                "by",
    468                                filter,
    469                                NULL);
    470     GNUNET_free (path);
    471   }
    472   if (NULL == sah->url)
    473     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    474   eh = TALER_MERCHANT_curl_easy_get_ (sah->url);
    475   if (NULL == eh)
    476     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    477   sah->job = GNUNET_CURL_job_add (sah->ctx,
    478                                   eh,
    479                                   &handle_get_statistics_amount_finished,
    480                                   sah);
    481   if (NULL == sah->job)
    482     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    483   return TALER_EC_NONE;
    484 }
    485 
    486 
    487 void
    488 TALER_MERCHANT_get_private_statistics_amount_cancel (
    489   struct TALER_MERCHANT_GetPrivateStatisticsAmountHandle *sah)
    490 {
    491   if (NULL != sah->job)
    492   {
    493     GNUNET_CURL_job_cancel (sah->job);
    494     sah->job = NULL;
    495   }
    496   GNUNET_free (sah->url);
    497   GNUNET_free (sah->base_url);
    498   GNUNET_free (sah->slug);
    499   GNUNET_free (sah);
    500 }
    501 
    502 
    503 /* end of merchant_api_get-private-statistics-amount-SLUG-new.c */