merchant

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

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


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