merchant

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

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


      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-amount-SLUG.c
     19  * @brief Implementation of the GET /private/statistics-amount/$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-amount/$SLUG operation.
     42  */
     43 struct TALER_MERCHANT_StatisticsAmountGetHandle
     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_StatisticsAmountGetCallback 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 buckets and intervals.
     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 sgh operation handle
     82  * @return #GNUNET_OK on success
     83  */
     84 static enum GNUNET_GenericReturnValue
     85 parse_intervals_and_buckets_amt (
     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_StatisticsAmountGetHandle *sgh
     92   )
     93 {
     94   unsigned int resp_buckets_len = json_array_size (jbuckets);
     95   unsigned int resp_intervals_len = json_array_size (jintervals);
     96 
     97   if ( (json_array_size (jbuckets) != (size_t)  resp_buckets_len) ||
     98        (json_array_size (jintervals) != (size_t)  resp_intervals_len) ||
     99        (resp_intervals_len = resp_buckets_len > MAX_STATISTICS) )
    100   {
    101     GNUNET_break (0);
    102     return GNUNET_SYSERR;
    103   }
    104   {
    105     struct TALER_MERCHANT_StatisticAmountByBucket resp_buckets[
    106       GNUNET_NZL (resp_buckets_len)];
    107     struct TALER_MERCHANT_StatisticAmountByInterval resp_intervals[
    108       GNUNET_NZL (resp_intervals_len)];
    109     size_t index;
    110     json_t *value;
    111     enum GNUNET_GenericReturnValue ret;
    112 
    113     ret = GNUNET_OK;
    114     json_array_foreach (jintervals, index, value) {
    115       struct TALER_MERCHANT_StatisticAmountByInterval *jinterval
    116         = &resp_intervals[index];
    117       const json_t *amounts_arr;
    118       size_t amounts_len;
    119 
    120       struct GNUNET_JSON_Specification spec[] = {
    121         GNUNET_JSON_spec_timestamp ("start_time",
    122                                     &jinterval->start_time),
    123         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    124                                       &amounts_arr),
    125         GNUNET_JSON_spec_end ()
    126       };
    127 
    128       if (GNUNET_OK !=
    129           GNUNET_JSON_parse (value,
    130                              spec,
    131                              NULL, NULL))
    132       {
    133         GNUNET_break_op (0);
    134         ret = GNUNET_SYSERR;
    135         continue;
    136       }
    137       if (GNUNET_SYSERR == ret)
    138         break;
    139       amounts_len = json_array_size (amounts_arr);
    140       {
    141         struct TALER_Amount amt_arr[amounts_len];
    142         size_t aindex;
    143         json_t *avalue;
    144 
    145         jinterval->cumulative_amount_len = amounts_len;
    146         jinterval->cumulative_amounts = amt_arr;
    147         json_array_foreach (amounts_arr, aindex, avalue) {
    148           if (! json_is_string (avalue))
    149           {
    150             GNUNET_break_op (0);
    151             return GNUNET_SYSERR;
    152           }
    153           if (GNUNET_OK !=
    154               TALER_string_to_amount (json_string_value (avalue),
    155                                       &amt_arr[aindex]))
    156           {
    157             GNUNET_break_op (0);
    158             return GNUNET_SYSERR;
    159           }
    160         }
    161       }
    162     }
    163     ret = GNUNET_OK;
    164     json_array_foreach (jbuckets, index, value) {
    165       struct TALER_MERCHANT_StatisticAmountByBucket *jbucket
    166         = &resp_buckets[index];
    167       const json_t *amounts_arr;
    168       size_t amounts_len;
    169       struct GNUNET_JSON_Specification spec[] = {
    170         GNUNET_JSON_spec_timestamp ("start_time",
    171                                     &jbucket->start_time),
    172         GNUNET_JSON_spec_timestamp ("end_time",
    173                                     &jbucket->end_time),
    174         GNUNET_JSON_spec_string ("range",
    175                                  &jbucket->range),
    176         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    177                                       &amounts_arr),
    178         GNUNET_JSON_spec_end ()
    179       };
    180 
    181       if (GNUNET_OK !=
    182           GNUNET_JSON_parse (value,
    183                              spec,
    184                              NULL, NULL))
    185       {
    186         GNUNET_break_op (0);
    187         ret = GNUNET_SYSERR;
    188         continue;
    189       }
    190       if (GNUNET_SYSERR == ret)
    191         break;
    192       amounts_len = json_array_size (amounts_arr);
    193       if (0 > amounts_len)
    194       {
    195         GNUNET_break_op (0);
    196         ret = GNUNET_SYSERR;
    197         break;
    198       }
    199       {
    200         struct TALER_Amount amt_arr[amounts_len];
    201         size_t aindex;
    202         json_t *avalue;
    203         jbucket->cumulative_amount_len = amounts_len;
    204         jbucket->cumulative_amounts = amt_arr;
    205         json_array_foreach (amounts_arr, aindex, avalue) {
    206           if (! json_is_string (avalue))
    207           {
    208             GNUNET_break_op (0);
    209             return GNUNET_SYSERR;
    210           }
    211           if (GNUNET_OK !=
    212               TALER_string_to_amount (json_string_value (avalue),
    213                                       &amt_arr[aindex]))
    214           {
    215             GNUNET_break_op (0);
    216             return GNUNET_SYSERR;
    217           }
    218         }
    219       }
    220     }
    221     if (GNUNET_OK == ret)
    222     {
    223       struct TALER_MERCHANT_StatisticsAmountGetResponse gsr = {
    224         .hr.http_status = MHD_HTTP_OK,
    225         .hr.reply = json,
    226         .details.ok.buckets_length = resp_buckets_len,
    227         .details.ok.buckets = resp_buckets,
    228         .details.ok.buckets_description = buckets_description,
    229         .details.ok.intervals_length = resp_intervals_len,
    230         .details.ok.intervals = resp_intervals,
    231         .details.ok.intervals_description = intervals_description,
    232       };
    233       sgh->cb (sgh->cb_cls,
    234                &gsr);
    235       sgh->cb = NULL; /* just to be sure */
    236     }
    237     return ret;
    238   }
    239 }
    240 
    241 
    242 /**
    243  * Function called when we're done processing the
    244  * HTTP GET /statistics-amount/$SLUG request.
    245  *
    246  * @param cls the `struct TALER_MERCHANT_StatisticsAmountGetHandle`
    247  * @param response_code HTTP response code, 0 on error
    248  * @param response response body, NULL if not in JSON
    249  */
    250 static void
    251 handle_get_statistics_amount_finished (void *cls,
    252                                        long response_code,
    253                                        const void *response)
    254 {
    255   struct TALER_MERCHANT_StatisticsAmountGetHandle *handle = cls;
    256   const json_t *json = response;
    257   struct TALER_MERCHANT_StatisticsAmountGetResponse res = {
    258     .hr.http_status = (unsigned int) response_code,
    259     .hr.reply = json
    260   };
    261 
    262   handle->job = NULL;
    263   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    264               "Got /statistics-amount/$SLUG response with status code %u\n",
    265               (unsigned int) response_code);
    266   switch (response_code)
    267   {
    268   case MHD_HTTP_OK:
    269     {
    270       const json_t *buckets;
    271       const json_t *intervals;
    272       const char *buckets_description = NULL;
    273       const char *intervals_description = NULL;
    274       struct GNUNET_JSON_Specification spec[] = {
    275         GNUNET_JSON_spec_array_const ("buckets",
    276                                       &buckets),
    277         GNUNET_JSON_spec_mark_optional (
    278           GNUNET_JSON_spec_string ("buckets_description",
    279                                    &buckets_description),
    280           NULL),
    281         GNUNET_JSON_spec_array_const ("intervals",
    282                                       &intervals),
    283         GNUNET_JSON_spec_mark_optional (
    284           GNUNET_JSON_spec_string ("intervals_description",
    285                                    &intervals_description),
    286           NULL),
    287         GNUNET_JSON_spec_end ()
    288       };
    289 
    290       if (GNUNET_OK !=
    291           GNUNET_JSON_parse (json,
    292                              spec,
    293                              NULL, NULL))
    294       {
    295         res.hr.http_status = 0;
    296         res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    297         break;
    298       }
    299       if (GNUNET_OK ==
    300           parse_intervals_and_buckets_amt (json,
    301                                            buckets,
    302                                            buckets_description,
    303                                            intervals,
    304                                            intervals_description,
    305                                            handle))
    306       {
    307         TALER_MERCHANT_statistic_amount_get_cancel (handle);
    308         return;
    309       }
    310       res.hr.http_status = 0;
    311       res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    312       break;
    313     }
    314   case MHD_HTTP_UNAUTHORIZED:
    315     res.hr.ec = TALER_JSON_get_error_code (json);
    316     res.hr.hint = TALER_JSON_get_error_hint (json);
    317     /* Nothing really to verify, merchant says we need to authenticate. */
    318     break;
    319   case MHD_HTTP_NOT_FOUND:
    320     res.hr.ec = TALER_JSON_get_error_code (json);
    321     res.hr.hint = TALER_JSON_get_error_hint (json);
    322     break;
    323   default:
    324     /* unexpected response code */
    325     res.hr.ec = TALER_JSON_get_error_code (json);
    326     res.hr.hint = TALER_JSON_get_error_hint (json);
    327     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    328                 "Unexpected response code %u/%d\n",
    329                 (unsigned int) response_code,
    330                 (int) res.hr.ec);
    331     break;
    332   }
    333 }
    334 
    335 
    336 struct TALER_MERCHANT_StatisticsAmountGetHandle *
    337 TALER_MERCHANT_statistic_amount_get (
    338   struct GNUNET_CURL_Context *ctx,
    339   const char *backend_url,
    340   const char *slug,
    341   enum TALER_MERCHANT_StatisticsType stype,
    342   TALER_MERCHANT_StatisticsAmountGetCallback cb,
    343   void *cb_cls)
    344 {
    345   struct TALER_MERCHANT_StatisticsAmountGetHandle *handle;
    346   CURL *eh;
    347 
    348   handle = GNUNET_new (struct TALER_MERCHANT_StatisticsAmountGetHandle);
    349   handle->ctx = ctx;
    350   handle->cb = cb;
    351   handle->cb_cls = cb_cls;
    352   {
    353     const char *filter = NULL;
    354     char *path;
    355 
    356     switch (stype)
    357     {
    358     case TALER_MERCHANT_STATISTICS_BY_BUCKET:
    359       filter = "bucket";
    360       break;
    361     case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
    362       filter = "interval";
    363       break;
    364     case TALER_MERCHANT_STATISTICS_ALL:
    365       filter = NULL;
    366       break;
    367     }
    368     GNUNET_asprintf (&path,
    369                      "private/statistics-amount/%s",
    370                      slug);
    371     handle->url = TALER_url_join (backend_url,
    372                                   path,
    373                                   "by",
    374                                   filter,
    375                                   NULL);
    376     GNUNET_free (path);
    377   }
    378   if (NULL == handle->url)
    379   {
    380     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    381                 "Could not construct request URL.\n");
    382     GNUNET_free (handle);
    383     return NULL;
    384   }
    385   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    386               "Requesting URL '%s'\n",
    387               handle->url);
    388   eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
    389   handle->job = GNUNET_CURL_job_add (ctx,
    390                                      eh,
    391                                      &handle_get_statistics_amount_finished,
    392                                      handle);
    393   return handle;
    394 }
    395 
    396 
    397 void
    398 TALER_MERCHANT_statistic_amount_get_cancel (
    399   struct TALER_MERCHANT_StatisticsAmountGetHandle *handle)
    400 {
    401   if (NULL != handle->job)
    402     GNUNET_CURL_job_cancel (handle->job);
    403   GNUNET_free (handle->url);
    404   GNUNET_free (handle);
    405 }
    406 
    407 
    408 /* end of merchant_api_get-private-statistics-amount-SLUG.c */