merchant

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

taler-merchant-httpd_get-private-statistics-report-transactions.c (20431B)


      1 /*
      2   This file is part of TALER
      3   (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 Affero General Public License as published by the Free Software
      7   Foundation; either version 3, 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-merchant-httpd_get-private-statistics-report-transactions.c
     18  * @brief implement GET /statistics-report/transactions
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include "taler-merchant-httpd_get-private-statistics-report-transactions.h"
     23 #include <gnunet/gnunet_json_lib.h>
     24 #include <taler/taler_json_lib.h>
     25 #include <taler/taler_mhd_lib.h>
     26 
     27 
     28 /**
     29  * Closure for the detail_cb().
     30  */
     31 struct ResponseContext
     32 {
     33   /**
     34    * Format of the response we are to generate.
     35    */
     36   enum
     37   {
     38     RCF_JSON,
     39     RCF_PDF
     40   } format;
     41 
     42   /**
     43    * Stored in a DLL while suspended.
     44    */
     45   struct ResponseContext *next;
     46 
     47   /**
     48    * Stored in a DLL while suspended.
     49    */
     50   struct ResponseContext *prev;
     51 
     52   /**
     53    * Context for this request.
     54    */
     55   struct TMH_HandlerContext *hc;
     56 
     57   /**
     58    * Async context used to run Typst.
     59    */
     60   struct TALER_MHD_TypstContext *tc;
     61 
     62   /**
     63    * Response to return.
     64    */
     65   struct MHD_Response *response;
     66 
     67   /**
     68    * Time when we started processing the request.
     69    */
     70   struct GNUNET_TIME_Timestamp now;
     71 
     72   /**
     73    * Period of each bucket.
     74    */
     75   struct GNUNET_TIME_Relative period;
     76 
     77   /**
     78    * Granularity of the buckets. Matches @e period.
     79    */
     80   const char *granularity;
     81 
     82   /**
     83    * Number of buckets to return.
     84    */
     85   uint64_t count;
     86 
     87   /**
     88    * HTTP status to use with @e response.
     89    */
     90   unsigned int http_status;
     91 
     92   /**
     93    * Length of the @e labels array.
     94    */
     95   unsigned int labels_cnt;
     96 
     97   /**
     98    * Array of labels for the chart.
     99    */
    100   char **labels;
    101 
    102   /**
    103    * Data groups for the chart.
    104    */
    105   json_t *data_groups;
    106 
    107   /**
    108    * #GNUNET_YES if connection was suspended,
    109    * #GNUNET_SYSERR if we were resumed on shutdown.
    110    */
    111   enum GNUNET_GenericReturnValue suspended;
    112 
    113 };
    114 
    115 
    116 /**
    117  * DLL of requests awaiting Typst.
    118  */
    119 static struct ResponseContext *rctx_head;
    120 
    121 /**
    122  * DLL of requests awaiting Typst.
    123  */
    124 static struct ResponseContext *rctx_tail;
    125 
    126 
    127 void
    128 TMH_handler_statistic_report_transactions_cleanup ()
    129 {
    130   struct ResponseContext *rctx;
    131 
    132   while (NULL != (rctx = rctx_head))
    133   {
    134     GNUNET_CONTAINER_DLL_remove (rctx_head,
    135                                  rctx_tail,
    136                                  rctx);
    137     rctx->suspended = GNUNET_SYSERR;
    138     MHD_resume_connection (rctx->hc->connection);
    139   }
    140 }
    141 
    142 
    143 /**
    144  * Free resources from @a ctx
    145  *
    146  * @param[in] ctx the `struct ResponseContext` to clean up
    147  */
    148 static void
    149 free_rc (void *ctx)
    150 {
    151   struct ResponseContext *rctx = ctx;
    152 
    153   if (NULL != rctx->tc)
    154   {
    155     TALER_MHD_typst_cancel (rctx->tc);
    156     rctx->tc = NULL;
    157   }
    158   if (NULL != rctx->response)
    159   {
    160     MHD_destroy_response (rctx->response);
    161     rctx->response = NULL;
    162   }
    163   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
    164     GNUNET_free (rctx->labels[i]);
    165   GNUNET_array_grow (rctx->labels,
    166                      rctx->labels_cnt,
    167                      0);
    168   json_decref (rctx->data_groups);
    169   GNUNET_free (rctx);
    170 }
    171 
    172 
    173 /**
    174  * Function called with the result of a #TALER_MHD_typst() operation.
    175  *
    176  * @param cls closure
    177  * @param tr result of the operation
    178  */
    179 static void
    180 pdf_cb (void *cls,
    181         const struct TALER_MHD_TypstResponse *tr)
    182 {
    183   struct ResponseContext *rctx = cls;
    184 
    185   rctx->tc = NULL;
    186   GNUNET_CONTAINER_DLL_remove (rctx_head,
    187                                rctx_tail,
    188                                rctx);
    189   rctx->suspended = GNUNET_NO;
    190   MHD_resume_connection (rctx->hc->connection);
    191   TALER_MHD_daemon_trigger ();
    192   if (TALER_EC_NONE != tr->ec)
    193   {
    194     rctx->http_status
    195       = TALER_ErrorCode_get_http_status (tr->ec);
    196     rctx->response
    197       = TALER_MHD_make_error (tr->ec,
    198                               tr->details.hint);
    199     return;
    200   }
    201   rctx->http_status
    202     = MHD_HTTP_OK;
    203   rctx->response
    204     = TALER_MHD_response_from_pdf_file (tr->details.filename);
    205 }
    206 
    207 
    208 /**
    209  * Typically called by `lookup_statistics_amount_by_bucket2`.
    210  *
    211  * @param[in,out] cls our `struct ResponseContext` to update
    212  * @param bucket_start start time of the bucket
    213  * @param amounts_len the length of @a amounts array
    214  * @param amounts the cumulative amounts in the bucket
    215  */
    216 static void
    217 amount_by_bucket (void *cls,
    218                   struct GNUNET_TIME_Timestamp bucket_start,
    219                   unsigned int amounts_len,
    220                   const struct TALER_Amount amounts[static amounts_len])
    221 {
    222   struct ResponseContext *rctx = cls;
    223   json_t *values;
    224 
    225   for (unsigned int i = 0; i<amounts_len; i++)
    226   {
    227     bool found = false;
    228 
    229     for (unsigned int j = 0; j<rctx->labels_cnt; j++)
    230     {
    231       if (0 == strcmp (amounts[i].currency,
    232                        rctx->labels[j]))
    233       {
    234         found = true;
    235         break;
    236       }
    237     }
    238     if (! found)
    239     {
    240       GNUNET_array_append (rctx->labels,
    241                            rctx->labels_cnt,
    242                            GNUNET_strdup (amounts[i].currency));
    243     }
    244   }
    245 
    246   values = json_array ();
    247   GNUNET_assert (NULL != values);
    248   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
    249   {
    250     const char *label = rctx->labels[i];
    251     double d = 0.0;
    252 
    253     for (unsigned int j = 0; j<amounts_len; j++)
    254     {
    255       const struct TALER_Amount *a = &amounts[j];
    256 
    257       if (0 != strcmp (amounts[j].currency,
    258                        label))
    259         continue;
    260       d = a->value * 1.0
    261           + (a->fraction * 1.0 / TALER_AMOUNT_FRAC_BASE);
    262       break;
    263     } /* for all amounts */
    264     GNUNET_assert (0 ==
    265                    json_array_append_new (values,
    266                                           json_real (d)));
    267   } /* for all labels */
    268 
    269   {
    270     json_t *dg;
    271 
    272     dg = GNUNET_JSON_PACK (
    273       GNUNET_JSON_pack_timestamp ("start_date",
    274                                   bucket_start),
    275       GNUNET_JSON_pack_array_steal ("values",
    276                                     values));
    277     GNUNET_assert (0 ==
    278                    json_array_append_new (rctx->data_groups,
    279                                           dg));
    280 
    281   }
    282 }
    283 
    284 
    285 /**
    286  * Create the transaction volume report.
    287  *
    288  * @param[in,out] rctx request context to use
    289  * @param[in,out] charts JSON chart array to expand
    290  * @return #GNUNET_OK on success,
    291  *         #GNUNET_NO to end with #MHD_YES,
    292  *         #GNUNET_NO to end with #MHD_NO.
    293  */
    294 static enum GNUNET_GenericReturnValue
    295 make_transaction_volume_report (struct ResponseContext *rctx,
    296                                 json_t *charts)
    297 {
    298   const char *bucket_name = "deposits-received";
    299   enum GNUNET_DB_QueryStatus qs;
    300   json_t *chart;
    301   json_t *labels;
    302 
    303   rctx->data_groups = json_array ();
    304   GNUNET_assert (NULL != rctx->data_groups);
    305   qs = TMH_db->lookup_statistics_amount_by_bucket2 (
    306     TMH_db->cls,
    307     rctx->hc->instance->settings.id,
    308     bucket_name,
    309     rctx->granularity,
    310     rctx->count,
    311     &amount_by_bucket,
    312     rctx);
    313   if (0 > qs)
    314   {
    315     GNUNET_break (0);
    316     return (MHD_YES ==
    317             TALER_MHD_reply_with_error (
    318               rctx->hc->connection,
    319               MHD_HTTP_INTERNAL_SERVER_ERROR,
    320               TALER_EC_GENERIC_DB_FETCH_FAILED,
    321               "lookup_statistics_amount_by_bucket2"))
    322         ? GNUNET_NO : GNUNET_SYSERR;
    323   }
    324   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    325   {
    326     json_decref (rctx->data_groups);
    327     rctx->data_groups = NULL;
    328     return GNUNET_OK;
    329   }
    330 
    331   labels = json_array ();
    332   GNUNET_assert (NULL != labels);
    333   for (unsigned int i=0; i<rctx->labels_cnt; i++)
    334   {
    335     GNUNET_assert (0 ==
    336                    json_array_append_new (labels,
    337                                           json_string (rctx->labels[i])));
    338     GNUNET_free (rctx->labels[i]);
    339   }
    340   GNUNET_array_grow (rctx->labels,
    341                      rctx->labels_cnt,
    342                      0);
    343   chart = GNUNET_JSON_PACK (
    344     GNUNET_JSON_pack_string ("chart_name",
    345                              "Sales volume"),
    346     GNUNET_JSON_pack_string ("y_label",
    347                              "Sales"),
    348     GNUNET_JSON_pack_array_steal ("data_groups",
    349                                   rctx->data_groups),
    350     GNUNET_JSON_pack_array_steal ("labels",
    351                                   labels),
    352     GNUNET_JSON_pack_bool ("cumulative",
    353                            false));
    354   rctx->data_groups = NULL;
    355   GNUNET_assert (0 ==
    356                  json_array_append_new (charts,
    357                                         chart));
    358   return GNUNET_OK;
    359 }
    360 
    361 
    362 /**
    363  * Typically called by `lookup_statistics_counter_by_bucket2`.
    364  *
    365  * @param[in,out] cls our `struct ResponseContext` to update
    366  * @param bucket_start start time of the bucket
    367  * @param counters_len the length of @a cumulative_amounts
    368  * @param descriptions description for the counter in the bucket
    369  * @param counters the counters in the bucket
    370  */
    371 static void
    372 count_by_bucket (void *cls,
    373                  struct GNUNET_TIME_Timestamp bucket_start,
    374                  unsigned int counters_len,
    375                  const char *descriptions[static counters_len],
    376                  uint64_t counters[static counters_len])
    377 {
    378   struct ResponseContext *rctx = cls;
    379   json_t *values;
    380 
    381   for (unsigned int i = 0; i<counters_len; i++)
    382   {
    383     bool found = false;
    384 
    385     for (unsigned int j = 0; j<rctx->labels_cnt; j++)
    386     {
    387       if (0 == strcmp (descriptions[i],
    388                        rctx->labels[j]))
    389       {
    390         found = true;
    391         break;
    392       }
    393     }
    394     if (! found)
    395     {
    396       GNUNET_array_append (rctx->labels,
    397                            rctx->labels_cnt,
    398                            GNUNET_strdup (descriptions[i]));
    399     }
    400   }
    401 
    402   values = json_array ();
    403   GNUNET_assert (NULL != values);
    404   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
    405   {
    406     const char *label = rctx->labels[i];
    407     uint64_t v = 0;
    408 
    409     for (unsigned int j = 0; j<counters_len; j++)
    410     {
    411       if (0 != strcmp (descriptions[j],
    412                        label))
    413         continue;
    414       v = counters[j];
    415       break;
    416     } /* for all amounts */
    417     GNUNET_assert (0 ==
    418                    json_array_append_new (values,
    419                                           json_integer (v)));
    420   } /* for all labels */
    421 
    422   {
    423     json_t *dg;
    424 
    425     dg = GNUNET_JSON_PACK (
    426       GNUNET_JSON_pack_timestamp ("start_date",
    427                                   bucket_start),
    428       GNUNET_JSON_pack_array_steal ("values",
    429                                     values));
    430     GNUNET_assert (0 ==
    431                    json_array_append_new (rctx->data_groups,
    432                                           dg));
    433 
    434   }
    435 }
    436 
    437 
    438 /**
    439  * Create the transaction count report.
    440  *
    441  * @param[in,out] rctx request context to use
    442  * @param[in,out] charts JSON chart array to expand
    443  * @return #GNUNET_OK on success,
    444  *         #GNUNET_NO to end with #MHD_YES,
    445  *         #GNUNET_NO to end with #MHD_NO.
    446  */
    447 static enum GNUNET_GenericReturnValue
    448 make_transaction_count_report (struct ResponseContext *rctx,
    449                                json_t *charts)
    450 {
    451   const char *prefix = "orders-paid";
    452   enum GNUNET_DB_QueryStatus qs;
    453   json_t *chart;
    454   json_t *labels;
    455 
    456   rctx->data_groups = json_array ();
    457   GNUNET_assert (NULL != rctx->data_groups);
    458   qs = TMH_db->lookup_statistics_counter_by_bucket2 (
    459     TMH_db->cls,
    460     rctx->hc->instance->settings.id,
    461     prefix,   /* prefix to match against bucket name */
    462     rctx->granularity,
    463     rctx->count,
    464     &count_by_bucket,
    465     rctx);
    466   if (0 > qs)
    467   {
    468     GNUNET_break (0);
    469     return (MHD_YES ==
    470             TALER_MHD_reply_with_error (
    471               rctx->hc->connection,
    472               MHD_HTTP_INTERNAL_SERVER_ERROR,
    473               TALER_EC_GENERIC_DB_FETCH_FAILED,
    474               "lookup_statistics_counter_by_bucket2"))
    475         ? GNUNET_NO : GNUNET_SYSERR;
    476   }
    477   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    478   {
    479     json_decref (rctx->data_groups);
    480     rctx->data_groups = NULL;
    481     return GNUNET_OK;
    482   }
    483   labels = json_array ();
    484   GNUNET_assert (NULL != labels);
    485   for (unsigned int i=0; i<rctx->labels_cnt; i++)
    486   {
    487     const char *label = rctx->labels[i];
    488 
    489     /* This condition should always hold. */
    490     if (0 ==
    491         strncmp (prefix,
    492                  label,
    493                  strlen (prefix)))
    494       label += strlen (prefix);
    495     GNUNET_assert (0 ==
    496                    json_array_append_new (labels,
    497                                           json_string (label)));
    498     GNUNET_free (rctx->labels[i]);
    499   }
    500   GNUNET_array_grow (rctx->labels,
    501                      rctx->labels_cnt,
    502                      0);
    503   chart = GNUNET_JSON_PACK (
    504     GNUNET_JSON_pack_string ("chart_name",
    505                              "Transaction counts"),
    506     GNUNET_JSON_pack_string ("y_label",
    507                              "Number of transactions"),
    508     GNUNET_JSON_pack_array_steal ("data_groups",
    509                                   rctx->data_groups),
    510     GNUNET_JSON_pack_array_steal ("labels",
    511                                   labels),
    512     GNUNET_JSON_pack_bool ("cumulative",
    513                            false));
    514   rctx->data_groups = NULL;
    515   GNUNET_assert (0 ==
    516                  json_array_append_new (charts,
    517                                         chart));
    518   return GNUNET_OK;
    519 }
    520 
    521 
    522 /**
    523  * Handle a GET "/private/statistics-report/transactions" request.
    524  *
    525  * @param rh context of the handler
    526  * @param connection the MHD connection to handle
    527  * @param[in,out] hc context with further information about the request
    528  * @return MHD result code
    529  */
    530 MHD_RESULT
    531 TMH_private_get_statistics_report_transactions (
    532   const struct TMH_RequestHandler *rh,
    533   struct MHD_Connection *connection,
    534   struct TMH_HandlerContext *hc)
    535 {
    536   struct ResponseContext *rctx = hc->ctx;
    537   struct TMH_MerchantInstance *mi = hc->instance;
    538   json_t *charts;
    539 
    540   if (NULL != rctx)
    541   {
    542     GNUNET_assert (GNUNET_YES != rctx->suspended);
    543     if (GNUNET_SYSERR == rctx->suspended)
    544       return MHD_NO;
    545     if (NULL == rctx->response)
    546     {
    547       GNUNET_break (0);
    548       return MHD_NO;
    549     }
    550     return MHD_queue_response (connection,
    551                                rctx->http_status,
    552                                rctx->response);
    553   }
    554   rctx = GNUNET_new (struct ResponseContext);
    555   rctx->hc = hc;
    556   rctx->now = GNUNET_TIME_timestamp_get ();
    557   hc->ctx = rctx;
    558   hc->cc = &free_rc;
    559   GNUNET_assert (NULL != mi);
    560 
    561   rctx->granularity = MHD_lookup_connection_value (connection,
    562                                                    MHD_GET_ARGUMENT_KIND,
    563                                                    "granularity");
    564   if (NULL == rctx->granularity)
    565   {
    566     rctx->granularity = "day";
    567     rctx->period = GNUNET_TIME_UNIT_DAYS;
    568     rctx->count = 95;
    569   }
    570   else
    571   {
    572     const struct
    573     {
    574       const char *name;
    575       struct GNUNET_TIME_Relative period;
    576       uint64_t default_counter;
    577     } map[] = {
    578       {
    579         .name = "second",
    580         .period = GNUNET_TIME_UNIT_SECONDS,
    581         .default_counter = 120,
    582       },
    583       {
    584         .name = "minute",
    585         .period = GNUNET_TIME_UNIT_MINUTES,
    586         .default_counter = 120,
    587       },
    588       {
    589         .name = "hour",
    590         .period = GNUNET_TIME_UNIT_HOURS,
    591         .default_counter = 48,
    592       },
    593       {
    594         .name = "day",
    595         .period = GNUNET_TIME_UNIT_DAYS,
    596         .default_counter = 95,
    597       },
    598       {
    599         .name = "month",
    600         .period = GNUNET_TIME_UNIT_MONTHS,
    601         .default_counter = 36,
    602       },
    603       {
    604         .name = "quarter",
    605         .period = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MONTHS,
    606                                                  3),
    607         .default_counter = 40,
    608       },
    609       {
    610         .name = "year",
    611         .period = GNUNET_TIME_UNIT_YEARS,
    612         .default_counter = 10
    613       },
    614       {
    615         .name = NULL
    616       }
    617     };
    618 
    619     rctx->count = 0;
    620     for (unsigned int i = 0; map[i].name != NULL; i++)
    621     {
    622       if (0 == strcasecmp (map[i].name,
    623                            rctx->granularity))
    624       {
    625         rctx->count = map[i].default_counter;
    626         rctx->period = map[i].period;
    627         break;
    628       }
    629     }
    630     if (0 == rctx->count)
    631     {
    632       GNUNET_break_op (0);
    633       return TALER_MHD_reply_with_error (
    634         connection,
    635         MHD_HTTP_BAD_REQUEST,
    636         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    637         "granularity");
    638     }
    639   } /* end handling granularity */
    640 
    641   /* Figure out desired output format */
    642   {
    643     const char *mime;
    644 
    645     mime = MHD_lookup_connection_value (connection,
    646                                         MHD_HEADER_KIND,
    647                                         MHD_HTTP_HEADER_ACCEPT);
    648     if (NULL == mime)
    649       mime = "application/json";
    650     if (0 == strcmp (mime,
    651                      "application/json"))
    652     {
    653       rctx->format = RCF_JSON;
    654     }
    655     else if (0 == strcmp (mime,
    656                           "application/pdf"))
    657     {
    658 
    659       rctx->format = RCF_PDF;
    660     }
    661     else
    662     {
    663       GNUNET_break_op (0);
    664       return TALER_MHD_REPLY_JSON_PACK (
    665         connection,
    666         MHD_HTTP_NOT_ACCEPTABLE,
    667         GNUNET_JSON_pack_string ("hint",
    668                                  mime));
    669     }
    670   } /* end of determine output format */
    671 
    672   TALER_MHD_parse_request_number (connection,
    673                                   "count",
    674                                   &rctx->count);
    675 
    676   /* create charts */
    677   charts = json_array ();
    678   GNUNET_assert (NULL != charts);
    679   {
    680     enum GNUNET_GenericReturnValue ret;
    681 
    682     ret = make_transaction_volume_report (rctx,
    683                                           charts);
    684     if (GNUNET_OK != ret)
    685       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
    686     ret = make_transaction_count_report (rctx,
    687                                          charts);
    688     if (GNUNET_OK != ret)
    689       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
    690   }
    691 
    692   /* generate response */
    693   {
    694     struct GNUNET_TIME_Timestamp start_date;
    695     struct GNUNET_TIME_Timestamp end_date;
    696     json_t *root;
    697 
    698     end_date = rctx->now;
    699     start_date
    700       = GNUNET_TIME_absolute_to_timestamp (
    701           GNUNET_TIME_absolute_subtract (
    702             end_date.abs_time,
    703             GNUNET_TIME_relative_multiply (rctx->period,
    704                                            rctx->count)));
    705     root = GNUNET_JSON_PACK (
    706       GNUNET_JSON_pack_string ("business_name",
    707                                mi->settings.name),
    708       GNUNET_JSON_pack_timestamp ("start_date",
    709                                   start_date),
    710       GNUNET_JSON_pack_timestamp ("end_date",
    711                                   end_date),
    712       GNUNET_JSON_pack_time_rel ("bucket_period",
    713                                  rctx->period),
    714       GNUNET_JSON_pack_array_steal ("charts",
    715                                     charts));
    716 
    717     switch (rctx->format)
    718     {
    719     case RCF_JSON:
    720       return TALER_MHD_reply_json (connection,
    721                                    root,
    722                                    MHD_HTTP_OK);
    723     case RCF_PDF:
    724       {
    725         struct TALER_MHD_TypstDocument doc = {
    726           .form_name = "transactions",
    727           .form_version = "0.0.0",
    728           .data = root
    729         };
    730 
    731         rctx->tc = TALER_MHD_typst (TMH_cfg,
    732                                     false, /* remove on exit */
    733                                     "merchant",
    734                                     1, /* one document, length of "array"! */
    735                                     &doc,
    736                                     &pdf_cb,
    737                                     rctx);
    738         json_decref (root);
    739         if (NULL == rctx->tc)
    740         {
    741           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    742                       "Client requested PDF, but Typst is unavailable\n");
    743           return TALER_MHD_reply_with_error (
    744             connection,
    745             MHD_HTTP_NOT_IMPLEMENTED,
    746             TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK,
    747             NULL);
    748         }
    749         GNUNET_CONTAINER_DLL_insert (rctx_head,
    750                                      rctx_tail,
    751                                      rctx);
    752         rctx->suspended = GNUNET_YES;
    753         MHD_suspend_connection (connection);
    754         return MHD_YES;
    755       }
    756     } /* end switch */
    757   }
    758   GNUNET_assert (0);
    759   return MHD_NO;
    760 }
    761 
    762 
    763 /* end of taler-merchant-httpd_get-private-statistics-report-transactions.c */