merchant

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

merchant_api_get_statistics.c (22057B)


      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_statistics.c
     19  * @brief Implementation of the GET /statistics-[counter,amount]/$SLUG request of the merchant's HTTP API
     20  * @author Martin Schanzenbach
     21  */
     22 #include "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_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  * Handle for a GET /statistics-counter/$SLUG operation.
     74  */
     75 struct TALER_MERCHANT_StatisticsCounterGetHandle
     76 {
     77   /**
     78    * The url for this request.
     79    */
     80   char *url;
     81 
     82   /**
     83    * Handle for the request.
     84    */
     85   struct GNUNET_CURL_Job *job;
     86 
     87   /**
     88    * Function to call with the result.
     89    */
     90   TALER_MERCHANT_StatisticsCounterGetCallback cb;
     91 
     92   /**
     93    * Closure for @a cb.
     94    */
     95   void *cb_cls;
     96 
     97   /**
     98    * Reference to the execution context.
     99    */
    100   struct GNUNET_CURL_Context *ctx;
    101 
    102 };
    103 
    104 
    105 /**
    106  * Parse interval information from buckets and intervals.
    107  *
    108  * @param json overall JSON reply
    109  * @param jbuckets JSON array (or NULL!) with bucket data
    110  * @param buckets_description human-readable description for the buckets
    111  * @param jintervals JSON array (or NULL!) with bucket data
    112  * @param intervals_description human-readable description for the intervals
    113  * @param sgh operation handle
    114  * @return #GNUNET_OK on success
    115  */
    116 static enum GNUNET_GenericReturnValue
    117 parse_intervals_and_buckets_amt (
    118   const json_t *json,
    119   const json_t *jbuckets,
    120   const char *buckets_description,
    121   const json_t *jintervals,
    122   const char *intervals_description,
    123   struct TALER_MERCHANT_StatisticsAmountGetHandle *sgh
    124   )
    125 {
    126   unsigned int resp_buckets_len = json_array_size (jbuckets);
    127   unsigned int resp_intervals_len = json_array_size (jintervals);
    128 
    129   if ( (json_array_size (jbuckets) != (size_t)  resp_buckets_len) ||
    130        (json_array_size (jintervals) != (size_t)  resp_intervals_len) ||
    131        (resp_intervals_len = resp_buckets_len > MAX_STATISTICS) )
    132   {
    133     GNUNET_break (0);
    134     return GNUNET_SYSERR;
    135   }
    136   {
    137     struct TALER_MERCHANT_StatisticAmountByBucket resp_buckets[
    138       GNUNET_NZL (resp_buckets_len)];
    139     struct TALER_MERCHANT_StatisticAmountByInterval resp_intervals[
    140       GNUNET_NZL (resp_intervals_len)];
    141     size_t index;
    142     json_t *value;
    143     enum GNUNET_GenericReturnValue ret;
    144 
    145     ret = GNUNET_OK;
    146     json_array_foreach (jintervals, index, value) {
    147       struct TALER_MERCHANT_StatisticAmountByInterval *jinterval
    148         = &resp_intervals[index];
    149       const json_t *amounts_arr;
    150       size_t amounts_len;
    151 
    152       struct GNUNET_JSON_Specification spec[] = {
    153         GNUNET_JSON_spec_timestamp ("start_time",
    154                                     &jinterval->start_time),
    155         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    156                                       &amounts_arr),
    157         GNUNET_JSON_spec_end ()
    158       };
    159 
    160       if (GNUNET_OK !=
    161           GNUNET_JSON_parse (value,
    162                              spec,
    163                              NULL, NULL))
    164       {
    165         GNUNET_break_op (0);
    166         ret = GNUNET_SYSERR;
    167         continue;
    168       }
    169       if (GNUNET_SYSERR == ret)
    170         break;
    171       amounts_len = json_array_size (amounts_arr);
    172       {
    173         struct TALER_Amount amt_arr[amounts_len];
    174         size_t aindex;
    175         json_t *avalue;
    176 
    177         jinterval->cumulative_amount_len = amounts_len;
    178         jinterval->cumulative_amounts = amt_arr;
    179         json_array_foreach (amounts_arr, aindex, avalue) {
    180           if (! json_is_string (avalue))
    181           {
    182             GNUNET_break_op (0);
    183             return GNUNET_SYSERR;
    184           }
    185           if (GNUNET_OK !=
    186               TALER_string_to_amount (json_string_value (avalue),
    187                                       &amt_arr[aindex]))
    188           {
    189             GNUNET_break_op (0);
    190             return GNUNET_SYSERR;
    191           }
    192         }
    193       }
    194     }
    195     ret = GNUNET_OK;
    196     json_array_foreach (jbuckets, index, value) {
    197       struct TALER_MERCHANT_StatisticAmountByBucket *jbucket
    198         = &resp_buckets[index];
    199       const json_t *amounts_arr;
    200       size_t amounts_len;
    201       struct GNUNET_JSON_Specification spec[] = {
    202         GNUNET_JSON_spec_timestamp ("start_time",
    203                                     &jbucket->start_time),
    204         GNUNET_JSON_spec_timestamp ("end_time",
    205                                     &jbucket->end_time),
    206         GNUNET_JSON_spec_string ("range",
    207                                  &jbucket->range),
    208         GNUNET_JSON_spec_array_const ("cumulative_amounts",
    209                                       &amounts_arr),
    210         GNUNET_JSON_spec_end ()
    211       };
    212 
    213       if (GNUNET_OK !=
    214           GNUNET_JSON_parse (value,
    215                              spec,
    216                              NULL, NULL))
    217       {
    218         GNUNET_break_op (0);
    219         ret = GNUNET_SYSERR;
    220         continue;
    221       }
    222       if (GNUNET_SYSERR == ret)
    223         break;
    224       amounts_len = json_array_size (amounts_arr);
    225       if (0 > amounts_len)
    226       {
    227         GNUNET_break_op (0);
    228         ret = GNUNET_SYSERR;
    229         break;
    230       }
    231       {
    232         struct TALER_Amount amt_arr[amounts_len];
    233         size_t aindex;
    234         json_t *avalue;
    235         jbucket->cumulative_amount_len = amounts_len;
    236         jbucket->cumulative_amounts = amt_arr;
    237         json_array_foreach (amounts_arr, aindex, avalue) {
    238           if (! json_is_string (avalue))
    239           {
    240             GNUNET_break_op (0);
    241             return GNUNET_SYSERR;
    242           }
    243           if (GNUNET_OK !=
    244               TALER_string_to_amount (json_string_value (avalue),
    245                                       &amt_arr[aindex]))
    246           {
    247             GNUNET_break_op (0);
    248             return GNUNET_SYSERR;
    249           }
    250         }
    251       }
    252     }
    253     if (GNUNET_OK == ret)
    254     {
    255       struct TALER_MERCHANT_StatisticsAmountGetResponse gsr = {
    256         .hr.http_status = MHD_HTTP_OK,
    257         .hr.reply = json,
    258         .details.ok.buckets_length = resp_buckets_len,
    259         .details.ok.buckets = resp_buckets,
    260         .details.ok.buckets_description = buckets_description,
    261         .details.ok.intervals_length = resp_intervals_len,
    262         .details.ok.intervals = resp_intervals,
    263         .details.ok.intervals_description = intervals_description,
    264       };
    265       sgh->cb (sgh->cb_cls,
    266                &gsr);
    267       sgh->cb = NULL; /* just to be sure */
    268     }
    269     return ret;
    270   }
    271 }
    272 
    273 
    274 /**
    275  * Function called when we're done processing the
    276  * HTTP GET /statistics-amount/$SLUG request.
    277  *
    278  * @param cls the `struct TALER_MERCHANT_StatisticsAmountGetHandle`
    279  * @param response_code HTTP response code, 0 on error
    280  * @param response response body, NULL if not in JSON
    281  */
    282 static void
    283 handle_get_statistics_amount_finished (void *cls,
    284                                        long response_code,
    285                                        const void *response)
    286 {
    287   struct TALER_MERCHANT_StatisticsAmountGetHandle *handle = cls;
    288   const json_t *json = response;
    289   struct TALER_MERCHANT_StatisticsAmountGetResponse res = {
    290     .hr.http_status = (unsigned int) response_code,
    291     .hr.reply = json
    292   };
    293 
    294   handle->job = NULL;
    295   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    296               "Got /statistics-amount/$SLUG response with status code %u\n",
    297               (unsigned int) response_code);
    298   switch (response_code)
    299   {
    300   case MHD_HTTP_OK:
    301     {
    302       const json_t *buckets;
    303       const json_t *intervals;
    304       const char *buckets_description = NULL;
    305       const char *intervals_description = NULL;
    306       struct GNUNET_JSON_Specification spec[] = {
    307         GNUNET_JSON_spec_array_const ("buckets",
    308                                       &buckets),
    309         GNUNET_JSON_spec_mark_optional (
    310           GNUNET_JSON_spec_string ("buckets_description",
    311                                    &buckets_description),
    312           NULL),
    313         GNUNET_JSON_spec_array_const ("intervals",
    314                                       &intervals),
    315         GNUNET_JSON_spec_mark_optional (
    316           GNUNET_JSON_spec_string ("intervals_description",
    317                                    &intervals_description),
    318           NULL),
    319         GNUNET_JSON_spec_end ()
    320       };
    321 
    322       if (GNUNET_OK !=
    323           GNUNET_JSON_parse (json,
    324                              spec,
    325                              NULL, NULL))
    326       {
    327         res.hr.http_status = 0;
    328         res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    329         break;
    330       }
    331       if (GNUNET_OK ==
    332           parse_intervals_and_buckets_amt (json,
    333                                            buckets,
    334                                            buckets_description,
    335                                            intervals,
    336                                            intervals_description,
    337                                            handle))
    338       {
    339         TALER_MERCHANT_statistic_amount_get_cancel (handle);
    340         return;
    341       }
    342       res.hr.http_status = 0;
    343       res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    344       break;
    345     }
    346   case MHD_HTTP_UNAUTHORIZED:
    347     res.hr.ec = TALER_JSON_get_error_code (json);
    348     res.hr.hint = TALER_JSON_get_error_hint (json);
    349     /* Nothing really to verify, merchant says we need to authenticate. */
    350     break;
    351   case MHD_HTTP_NOT_FOUND:
    352     res.hr.ec = TALER_JSON_get_error_code (json);
    353     res.hr.hint = TALER_JSON_get_error_hint (json);
    354     break;
    355   default:
    356     /* unexpected response code */
    357     res.hr.ec = TALER_JSON_get_error_code (json);
    358     res.hr.hint = TALER_JSON_get_error_hint (json);
    359     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    360                 "Unexpected response code %u/%d\n",
    361                 (unsigned int) response_code,
    362                 (int) res.hr.ec);
    363     break;
    364   }
    365 }
    366 
    367 
    368 /**
    369  * Parse interval information from @a ia.
    370  *
    371  * @param json overall JSON reply
    372  * @param jbuckets JSON array (or NULL!) with bucket data
    373  * @param buckets_description human-readable description for the buckets
    374  * @param jintervals JSON array (or NULL!) with bucket data
    375  * @param intervals_description human-readable description for the intervals
    376  * @param scgh operation handle
    377  * @return #GNUNET_OK on success
    378  */
    379 static enum GNUNET_GenericReturnValue
    380 parse_intervals_and_buckets (
    381   const json_t *json,
    382   const json_t *jbuckets,
    383   const char *buckets_description,
    384   const json_t *jintervals,
    385   const char *intervals_description,
    386   struct TALER_MERCHANT_StatisticsCounterGetHandle *scgh)
    387 {
    388   unsigned int resp_buckets_len = json_array_size (jbuckets);
    389   unsigned int resp_intervals_len = json_array_size (jintervals);
    390 
    391   if ( (json_array_size (jbuckets) != (size_t)  resp_buckets_len) ||
    392        (json_array_size (jintervals) != (size_t)  resp_intervals_len) ||
    393        (resp_intervals_len = resp_buckets_len > MAX_STATISTICS) )
    394   {
    395     GNUNET_break (0);
    396     return GNUNET_SYSERR;
    397   }
    398   {
    399     struct TALER_MERCHANT_StatisticCounterByBucket resp_buckets[
    400       GNUNET_NZL (resp_buckets_len)];
    401     struct TALER_MERCHANT_StatisticCounterByInterval resp_intervals[
    402       GNUNET_NZL (resp_intervals_len)];
    403     size_t index;
    404     json_t *value;
    405     enum GNUNET_GenericReturnValue ret;
    406 
    407     ret = GNUNET_OK;
    408     json_array_foreach (jintervals, index, value) {
    409       struct TALER_MERCHANT_StatisticCounterByInterval *jinterval
    410         = &resp_intervals[index];
    411       struct GNUNET_JSON_Specification spec[] = {
    412         GNUNET_JSON_spec_timestamp ("start_time",
    413                                     &jinterval->start_time),
    414         GNUNET_JSON_spec_uint64 ("cumulative_counter",
    415                                  &jinterval->cumulative_counter),
    416         GNUNET_JSON_spec_end ()
    417       };
    418 
    419       if (GNUNET_OK !=
    420           GNUNET_JSON_parse (value,
    421                              spec,
    422                              NULL, NULL))
    423       {
    424         GNUNET_break_op (0);
    425         ret = GNUNET_SYSERR;
    426         continue;
    427       }
    428       if (GNUNET_SYSERR == ret)
    429         break;
    430     }
    431     ret = GNUNET_OK;
    432     json_array_foreach (jbuckets, index, value) {
    433       struct TALER_MERCHANT_StatisticCounterByBucket *jbucket = &resp_buckets[
    434         index];
    435       struct GNUNET_JSON_Specification spec[] = {
    436         GNUNET_JSON_spec_timestamp ("start_time",
    437                                     &jbucket->start_time),
    438         GNUNET_JSON_spec_timestamp ("end_time",
    439                                     &jbucket->end_time),
    440         GNUNET_JSON_spec_string ("range",
    441                                  &jbucket->range),
    442         GNUNET_JSON_spec_uint64 ("cumulative_counter",
    443                                  &jbucket->cumulative_counter),
    444         GNUNET_JSON_spec_end ()
    445       };
    446 
    447       if (GNUNET_OK !=
    448           GNUNET_JSON_parse (value,
    449                              spec,
    450                              NULL, NULL))
    451       {
    452         GNUNET_break_op (0);
    453         ret = GNUNET_SYSERR;
    454         continue;
    455       }
    456       if (GNUNET_SYSERR == ret)
    457         break;
    458     }
    459     if (GNUNET_OK == ret)
    460     {
    461       struct TALER_MERCHANT_StatisticsCounterGetResponse gsr = {
    462         .hr.http_status = MHD_HTTP_OK,
    463         .hr.reply = json,
    464         .details.ok.buckets_length = resp_buckets_len,
    465         .details.ok.buckets = resp_buckets,
    466         .details.ok.buckets_description = buckets_description,
    467         .details.ok.intervals_length = resp_intervals_len,
    468         .details.ok.intervals = resp_intervals,
    469         .details.ok.intervals_description = intervals_description,
    470       };
    471       scgh->cb (scgh->cb_cls,
    472                 &gsr);
    473       scgh->cb = NULL; /* just to be sure */
    474     }
    475     return ret;
    476   }
    477 }
    478 
    479 
    480 /**
    481  * Function called when we're done processing the
    482  * HTTP GET /statistics-counter/$SLUG request.
    483  *
    484  * @param cls the `struct TALER_MERCHANT_StatisticsCounterGetHandle`
    485  * @param response_code HTTP response code, 0 on error
    486  * @param response response body, NULL if not in JSON
    487  */
    488 static void
    489 handle_get_statistics_counter_finished (void *cls,
    490                                         long response_code,
    491                                         const void *response)
    492 {
    493   struct TALER_MERCHANT_StatisticsCounterGetHandle *handle = cls;
    494   const json_t *json = response;
    495   struct TALER_MERCHANT_StatisticsCounterGetResponse res = {
    496     .hr.http_status = (unsigned int) response_code,
    497     .hr.reply = json
    498   };
    499 
    500   handle->job = NULL;
    501   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    502               "Got /statistics-counter/$SLUG response with status code %u\n",
    503               (unsigned int) response_code);
    504   switch (response_code)
    505   {
    506   case MHD_HTTP_OK:
    507     {
    508       const json_t *buckets;
    509       const json_t *intervals;
    510       const char *buckets_description;
    511       const char *intervals_description;
    512       struct GNUNET_JSON_Specification spec[] = {
    513         GNUNET_JSON_spec_array_const ("buckets",
    514                                       &buckets),
    515         GNUNET_JSON_spec_mark_optional (
    516           GNUNET_JSON_spec_string ("buckets_description",
    517                                    &buckets_description),
    518           NULL),
    519         GNUNET_JSON_spec_array_const ("intervals",
    520                                       &intervals),
    521         GNUNET_JSON_spec_mark_optional (
    522           GNUNET_JSON_spec_string ("intervals_description",
    523                                    &intervals_description),
    524           NULL),
    525         GNUNET_JSON_spec_end ()
    526       };
    527 
    528       if (GNUNET_OK !=
    529           GNUNET_JSON_parse (json,
    530                              spec,
    531                              NULL, NULL))
    532       {
    533         res.hr.http_status = 0;
    534         res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    535         break;
    536       }
    537       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    538                   "%s\n", json_dumps (json, JSON_INDENT (1)));
    539       if (GNUNET_OK ==
    540           parse_intervals_and_buckets (json,
    541                                        buckets,
    542                                        buckets_description,
    543                                        intervals,
    544                                        intervals_description,
    545                                        handle))
    546       {
    547         TALER_MERCHANT_statistic_counter_get_cancel (handle);
    548         return;
    549       }
    550       res.hr.http_status = 0;
    551       res.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    552       break;
    553     }
    554   case MHD_HTTP_UNAUTHORIZED:
    555     res.hr.ec = TALER_JSON_get_error_code (json);
    556     res.hr.hint = TALER_JSON_get_error_hint (json);
    557     /* Nothing really to verify, merchant says we need to authenticate. */
    558     break;
    559   case MHD_HTTP_NOT_FOUND:
    560     res.hr.ec = TALER_JSON_get_error_code (json);
    561     res.hr.hint = TALER_JSON_get_error_hint (json);
    562     break;
    563   default:
    564     /* unexpected response code */
    565     res.hr.ec = TALER_JSON_get_error_code (json);
    566     res.hr.hint = TALER_JSON_get_error_hint (json);
    567     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    568                 "Unexpected response code %u/%d\n",
    569                 (unsigned int) response_code,
    570                 (int) res.hr.ec);
    571     break;
    572   }
    573 }
    574 
    575 
    576 struct TALER_MERCHANT_StatisticsCounterGetHandle *
    577 TALER_MERCHANT_statistic_counter_get (
    578   struct GNUNET_CURL_Context *ctx,
    579   const char *backend_url,
    580   const char *slug,
    581   enum TALER_MERCHANT_StatisticsType stype,
    582   TALER_MERCHANT_StatisticsCounterGetCallback cb,
    583   void *cb_cls)
    584 {
    585   struct TALER_MERCHANT_StatisticsCounterGetHandle *handle;
    586   CURL *eh;
    587 
    588   handle = GNUNET_new (struct TALER_MERCHANT_StatisticsCounterGetHandle);
    589   handle->ctx = ctx;
    590   handle->cb = cb;
    591   handle->cb_cls = cb_cls;
    592   {
    593     const char *filter = NULL;
    594     char *path;
    595 
    596     switch (stype)
    597     {
    598     case TALER_MERCHANT_STATISTICS_BY_BUCKET:
    599       filter = "bucket";
    600       break;
    601     case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
    602       filter = "interval";
    603       break;
    604     case TALER_MERCHANT_STATISTICS_ALL:
    605       filter = NULL;
    606       break;
    607     }
    608     GNUNET_asprintf (&path,
    609                      "private/statistics-counter/%s",
    610                      slug);
    611     handle->url = TALER_url_join (backend_url,
    612                                   path,
    613                                   "by",
    614                                   filter,
    615                                   NULL);
    616     GNUNET_free (path);
    617   }
    618   if (NULL == handle->url)
    619   {
    620     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    621                 "Could not construct request URL.\n");
    622     GNUNET_free (handle);
    623     return NULL;
    624   }
    625   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    626               "Requesting URL '%s'\n",
    627               handle->url);
    628   eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
    629   handle->job = GNUNET_CURL_job_add (ctx,
    630                                      eh,
    631                                      &handle_get_statistics_counter_finished,
    632                                      handle);
    633   return handle;
    634 }
    635 
    636 
    637 void
    638 TALER_MERCHANT_statistic_counter_get_cancel (
    639   struct TALER_MERCHANT_StatisticsCounterGetHandle *handle)
    640 {
    641   if (NULL != handle->job)
    642     GNUNET_CURL_job_cancel (handle->job);
    643   GNUNET_free (handle->url);
    644   GNUNET_free (handle);
    645 }
    646 
    647 
    648 struct TALER_MERCHANT_StatisticsAmountGetHandle *
    649 TALER_MERCHANT_statistic_amount_get (
    650   struct GNUNET_CURL_Context *ctx,
    651   const char *backend_url,
    652   const char *slug,
    653   enum TALER_MERCHANT_StatisticsType stype,
    654   TALER_MERCHANT_StatisticsAmountGetCallback cb,
    655   void *cb_cls)
    656 {
    657   struct TALER_MERCHANT_StatisticsAmountGetHandle *handle;
    658   CURL *eh;
    659 
    660   handle = GNUNET_new (struct TALER_MERCHANT_StatisticsAmountGetHandle);
    661   handle->ctx = ctx;
    662   handle->cb = cb;
    663   handle->cb_cls = cb_cls;
    664   {
    665     const char *filter = NULL;
    666     char *path;
    667 
    668     switch (stype)
    669     {
    670     case TALER_MERCHANT_STATISTICS_BY_BUCKET:
    671       filter = "bucket";
    672       break;
    673     case TALER_MERCHANT_STATISTICS_BY_INTERVAL:
    674       filter = "interval";
    675       break;
    676     case TALER_MERCHANT_STATISTICS_ALL:
    677       filter = NULL;
    678       break;
    679     }
    680     GNUNET_asprintf (&path,
    681                      "private/statistics-amount/%s",
    682                      slug);
    683     handle->url = TALER_url_join (backend_url,
    684                                   path,
    685                                   "by",
    686                                   filter,
    687                                   NULL);
    688     GNUNET_free (path);
    689   }
    690   if (NULL == handle->url)
    691   {
    692     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    693                 "Could not construct request URL.\n");
    694     GNUNET_free (handle);
    695     return NULL;
    696   }
    697   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    698               "Requesting URL '%s'\n",
    699               handle->url);
    700   eh = TALER_MERCHANT_curl_easy_get_ (handle->url);
    701   handle->job = GNUNET_CURL_job_add (ctx,
    702                                      eh,
    703                                      &handle_get_statistics_amount_finished,
    704                                      handle);
    705   return handle;
    706 }
    707 
    708 
    709 void
    710 TALER_MERCHANT_statistic_amount_get_cancel (
    711   struct TALER_MERCHANT_StatisticsAmountGetHandle *handle)
    712 {
    713   if (NULL != handle->job)
    714     GNUNET_CURL_job_cancel (handle->job);
    715   GNUNET_free (handle->url);
    716   GNUNET_free (handle);
    717 }