merchant

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

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


      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 src/backend/taler-merchant-httpd_get-private-statistics-report-transactions.c
     18  * @brief implement GET /statistics-report/transactions
     19  * @author Christian Grothoff
     20  */
     21 #include "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 #include "taler/taler_merchant_util.h"
     27 #include "merchant-database/lookup_statistics_amount_by_bucket2.h"
     28 #include "merchant-database/lookup_statistics_counter_by_bucket2.h"
     29 
     30 
     31 /**
     32  * Closure for the detail_cb().
     33  */
     34 struct ResponseContext
     35 {
     36   /**
     37    * Format of the response we are to generate.
     38    */
     39   enum
     40   {
     41     RCF_JSON,
     42     RCF_PDF
     43   } format;
     44 
     45   /**
     46    * Stored in a DLL while suspended.
     47    */
     48   struct ResponseContext *next;
     49 
     50   /**
     51    * Stored in a DLL while suspended.
     52    */
     53   struct ResponseContext *prev;
     54 
     55   /**
     56    * Context for this request.
     57    */
     58   struct TMH_HandlerContext *hc;
     59 
     60   /**
     61    * Async context used to run Typst.
     62    */
     63   struct TALER_MHD_TypstContext *tc;
     64 
     65   /**
     66    * Response to return.
     67    */
     68   struct MHD_Response *response;
     69 
     70   /**
     71    * Time when we started processing the request.
     72    */
     73   struct GNUNET_TIME_Timestamp now;
     74 
     75   /**
     76    * Period of each bucket.
     77    */
     78   struct GNUNET_TIME_Relative period;
     79 
     80   /**
     81    * Granularity of the buckets. Matches @e period.
     82    */
     83   const char *granularity;
     84 
     85   /**
     86    * Number of buckets to return.
     87    */
     88   uint64_t count;
     89 
     90   /**
     91    * HTTP status to use with @e response.
     92    */
     93   unsigned int http_status;
     94 
     95   /**
     96    * Length of the @e labels array.
     97    */
     98   unsigned int labels_cnt;
     99 
    100   /**
    101    * Array of labels for the chart.
    102    */
    103   char **labels;
    104 
    105   /**
    106    * Data groups for the chart.
    107    */
    108   json_t *data_groups;
    109 
    110   /**
    111    * #GNUNET_YES if connection was suspended,
    112    * #GNUNET_SYSERR if we were resumed on shutdown.
    113    */
    114   enum GNUNET_GenericReturnValue suspended;
    115 
    116 };
    117 
    118 
    119 /**
    120  * DLL of requests awaiting Typst.
    121  */
    122 static struct ResponseContext *rctx_head;
    123 
    124 /**
    125  * DLL of requests awaiting Typst.
    126  */
    127 static struct ResponseContext *rctx_tail;
    128 
    129 
    130 void
    131 TMH_handler_statistic_report_transactions_cleanup ()
    132 {
    133   struct ResponseContext *rctx;
    134 
    135   while (NULL != (rctx = rctx_head))
    136   {
    137     GNUNET_CONTAINER_DLL_remove (rctx_head,
    138                                  rctx_tail,
    139                                  rctx);
    140     rctx->suspended = GNUNET_SYSERR;
    141     MHD_resume_connection (rctx->hc->connection);
    142   }
    143 }
    144 
    145 
    146 /**
    147  * Free resources from @a ctx
    148  *
    149  * @param[in] ctx the `struct ResponseContext` to clean up
    150  */
    151 static void
    152 free_rc (void *ctx)
    153 {
    154   struct ResponseContext *rctx = ctx;
    155 
    156   if (NULL != rctx->tc)
    157   {
    158     TALER_MHD_typst_cancel (rctx->tc);
    159     rctx->tc = NULL;
    160   }
    161   if (NULL != rctx->response)
    162   {
    163     MHD_destroy_response (rctx->response);
    164     rctx->response = NULL;
    165   }
    166   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
    167     GNUNET_free (rctx->labels[i]);
    168   GNUNET_array_grow (rctx->labels,
    169                      rctx->labels_cnt,
    170                      0);
    171   json_decref (rctx->data_groups);
    172   GNUNET_free (rctx);
    173 }
    174 
    175 
    176 /**
    177  * Function called with the result of a #TALER_MHD_typst() operation.
    178  *
    179  * @param cls closure
    180  * @param tr result of the operation
    181  */
    182 static void
    183 pdf_cb (void *cls,
    184         const struct TALER_MHD_TypstResponse *tr)
    185 {
    186   struct ResponseContext *rctx = cls;
    187 
    188   rctx->tc = NULL;
    189   GNUNET_CONTAINER_DLL_remove (rctx_head,
    190                                rctx_tail,
    191                                rctx);
    192   rctx->suspended = GNUNET_NO;
    193   MHD_resume_connection (rctx->hc->connection);
    194   TALER_MHD_daemon_trigger ();
    195   if (TALER_EC_NONE != tr->ec)
    196   {
    197     rctx->http_status
    198       = TALER_ErrorCode_get_http_status (tr->ec);
    199     rctx->response
    200       = TALER_MHD_make_error (tr->ec,
    201                               tr->details.hint);
    202     return;
    203   }
    204   rctx->http_status
    205     = MHD_HTTP_OK;
    206   rctx->response
    207     = TALER_MHD_response_from_pdf_file (tr->details.filename);
    208 }
    209 
    210 
    211 /**
    212  * Typically called by `lookup_statistics_amount_by_bucket2`.
    213  *
    214  * @param[in,out] cls our `struct ResponseContext` to update
    215  * @param bucket_start start time of the bucket
    216  * @param amounts_len the length of @a amounts array
    217  * @param amounts the cumulative amounts in the bucket
    218  */
    219 static void
    220 amount_by_bucket (void *cls,
    221                   struct GNUNET_TIME_Timestamp bucket_start,
    222                   unsigned int amounts_len,
    223                   const struct TALER_Amount amounts[static amounts_len])
    224 {
    225   struct ResponseContext *rctx = cls;
    226   json_t *values;
    227 
    228   for (unsigned int i = 0; i<amounts_len; i++)
    229   {
    230     bool found = false;
    231 
    232     for (unsigned int j = 0; j<rctx->labels_cnt; j++)
    233     {
    234       if (0 == strcmp (amounts[i].currency,
    235                        rctx->labels[j]))
    236       {
    237         found = true;
    238         break;
    239       }
    240     }
    241     if (! found)
    242     {
    243       GNUNET_array_append (rctx->labels,
    244                            rctx->labels_cnt,
    245                            GNUNET_strdup (amounts[i].currency));
    246     }
    247   }
    248 
    249   values = json_array ();
    250   GNUNET_assert (NULL != values);
    251   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
    252   {
    253     const char *label = rctx->labels[i];
    254     double d = 0.0;
    255 
    256     for (unsigned int j = 0; j<amounts_len; j++)
    257     {
    258       const struct TALER_Amount *a = &amounts[j];
    259 
    260       if (0 != strcmp (amounts[j].currency,
    261                        label))
    262         continue;
    263       d = a->value * 1.0
    264           + (a->fraction * 1.0 / TALER_AMOUNT_FRAC_BASE);
    265       break;
    266     } /* for all amounts */
    267     GNUNET_assert (0 ==
    268                    json_array_append_new (values,
    269                                           json_real (d)));
    270   } /* for all labels */
    271 
    272   {
    273     json_t *dg;
    274 
    275     dg = GNUNET_JSON_PACK (
    276       GNUNET_JSON_pack_timestamp ("start_date",
    277                                   bucket_start),
    278       GNUNET_JSON_pack_array_steal ("values",
    279                                     values));
    280     GNUNET_assert (0 ==
    281                    json_array_append_new (rctx->data_groups,
    282                                           dg));
    283 
    284   }
    285 }
    286 
    287 
    288 /**
    289  * Create the transaction volume report.
    290  *
    291  * @param[in,out] rctx request context to use
    292  * @param[in,out] charts JSON chart array to expand
    293  * @return #GNUNET_OK on success,
    294  *         #GNUNET_NO to end with #MHD_YES,
    295  *         #GNUNET_NO to end with #MHD_NO.
    296  */
    297 static enum GNUNET_GenericReturnValue
    298 make_transaction_volume_report (struct ResponseContext *rctx,
    299                                 json_t *charts)
    300 {
    301   const char *bucket_name = "deposits-received";
    302   enum GNUNET_DB_QueryStatus qs;
    303   json_t *chart;
    304   json_t *labels;
    305 
    306   rctx->data_groups = json_array ();
    307   GNUNET_assert (NULL != rctx->data_groups);
    308   qs = TALER_MERCHANTDB_lookup_statistics_amount_by_bucket2 (
    309     TMH_db,
    310     rctx->hc->instance->settings.id,
    311     bucket_name,
    312     rctx->granularity,
    313     rctx->count,
    314     &amount_by_bucket,
    315     rctx);
    316   if (0 > qs)
    317   {
    318     GNUNET_break (0);
    319     return (MHD_YES ==
    320             TALER_MHD_reply_with_error (
    321               rctx->hc->connection,
    322               MHD_HTTP_INTERNAL_SERVER_ERROR,
    323               TALER_EC_GENERIC_DB_FETCH_FAILED,
    324               "lookup_statistics_amount_by_bucket2"))
    325         ? GNUNET_NO : GNUNET_SYSERR;
    326   }
    327   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    328   {
    329     json_decref (rctx->data_groups);
    330     rctx->data_groups = NULL;
    331     return GNUNET_OK;
    332   }
    333 
    334   labels = json_array ();
    335   GNUNET_assert (NULL != labels);
    336   for (unsigned int i=0; i<rctx->labels_cnt; i++)
    337   {
    338     GNUNET_assert (0 ==
    339                    json_array_append_new (labels,
    340                                           json_string (rctx->labels[i])));
    341     GNUNET_free (rctx->labels[i]);
    342   }
    343   GNUNET_array_grow (rctx->labels,
    344                      rctx->labels_cnt,
    345                      0);
    346   chart = GNUNET_JSON_PACK (
    347     GNUNET_JSON_pack_string ("chart_name",
    348                              "Sales volume"),
    349     GNUNET_JSON_pack_string ("y_label",
    350                              "Sales"),
    351     GNUNET_JSON_pack_array_steal ("data_groups",
    352                                   rctx->data_groups),
    353     GNUNET_JSON_pack_array_steal ("labels",
    354                                   labels),
    355     GNUNET_JSON_pack_bool ("cumulative",
    356                            false));
    357   rctx->data_groups = NULL;
    358   GNUNET_assert (0 ==
    359                  json_array_append_new (charts,
    360                                         chart));
    361   return GNUNET_OK;
    362 }
    363 
    364 
    365 /**
    366  * Typically called by `lookup_statistics_counter_by_bucket2`.
    367  *
    368  * @param[in,out] cls our `struct ResponseContext` to update
    369  * @param bucket_start start time of the bucket
    370  * @param counters_len the length of @a cumulative_amounts
    371  * @param descriptions description for the counter in the bucket
    372  * @param counters the counters in the bucket
    373  */
    374 static void
    375 count_by_bucket (void *cls,
    376                  struct GNUNET_TIME_Timestamp bucket_start,
    377                  unsigned int counters_len,
    378                  const char *descriptions[static counters_len],
    379                  uint64_t counters[static counters_len])
    380 {
    381   struct ResponseContext *rctx = cls;
    382   json_t *values;
    383 
    384   for (unsigned int i = 0; i<counters_len; i++)
    385   {
    386     bool found = false;
    387 
    388     for (unsigned int j = 0; j<rctx->labels_cnt; j++)
    389     {
    390       if (0 == strcmp (descriptions[i],
    391                        rctx->labels[j]))
    392       {
    393         found = true;
    394         break;
    395       }
    396     }
    397     if (! found)
    398     {
    399       GNUNET_array_append (rctx->labels,
    400                            rctx->labels_cnt,
    401                            GNUNET_strdup (descriptions[i]));
    402     }
    403   }
    404 
    405   values = json_array ();
    406   GNUNET_assert (NULL != values);
    407   for (unsigned int i = 0; i<rctx->labels_cnt; i++)
    408   {
    409     const char *label = rctx->labels[i];
    410     uint64_t v = 0;
    411 
    412     for (unsigned int j = 0; j<counters_len; j++)
    413     {
    414       if (0 != strcmp (descriptions[j],
    415                        label))
    416         continue;
    417       v = counters[j];
    418       break;
    419     } /* for all amounts */
    420     GNUNET_assert (0 ==
    421                    json_array_append_new (values,
    422                                           json_integer (v)));
    423   } /* for all labels */
    424 
    425   {
    426     json_t *dg;
    427 
    428     dg = GNUNET_JSON_PACK (
    429       GNUNET_JSON_pack_timestamp ("start_date",
    430                                   bucket_start),
    431       GNUNET_JSON_pack_array_steal ("values",
    432                                     values));
    433     GNUNET_assert (0 ==
    434                    json_array_append_new (rctx->data_groups,
    435                                           dg));
    436 
    437   }
    438 }
    439 
    440 
    441 /**
    442  * Create the transaction count report.
    443  *
    444  * @param[in,out] rctx request context to use
    445  * @param[in,out] charts JSON chart array to expand
    446  * @return #GNUNET_OK on success,
    447  *         #GNUNET_NO to end with #MHD_YES,
    448  *         #GNUNET_NO to end with #MHD_NO.
    449  */
    450 static enum GNUNET_GenericReturnValue
    451 make_transaction_count_report (struct ResponseContext *rctx,
    452                                json_t *charts)
    453 {
    454   const char *prefix = "orders-paid";
    455   enum GNUNET_DB_QueryStatus qs;
    456   json_t *chart;
    457   json_t *labels;
    458 
    459   rctx->data_groups = json_array ();
    460   GNUNET_assert (NULL != rctx->data_groups);
    461   qs = TALER_MERCHANTDB_lookup_statistics_counter_by_bucket2 (
    462     TMH_db,
    463     rctx->hc->instance->settings.id,
    464     prefix,   /* prefix to match against bucket name */
    465     rctx->granularity,
    466     rctx->count,
    467     &count_by_bucket,
    468     rctx);
    469   if (0 > qs)
    470   {
    471     GNUNET_break (0);
    472     return (MHD_YES ==
    473             TALER_MHD_reply_with_error (
    474               rctx->hc->connection,
    475               MHD_HTTP_INTERNAL_SERVER_ERROR,
    476               TALER_EC_GENERIC_DB_FETCH_FAILED,
    477               "lookup_statistics_counter_by_bucket2"))
    478         ? GNUNET_NO : GNUNET_SYSERR;
    479   }
    480   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    481   {
    482     json_decref (rctx->data_groups);
    483     rctx->data_groups = NULL;
    484     return GNUNET_OK;
    485   }
    486   labels = json_array ();
    487   GNUNET_assert (NULL != labels);
    488   for (unsigned int i=0; i<rctx->labels_cnt; i++)
    489   {
    490     const char *label = rctx->labels[i];
    491 
    492     /* This condition should always hold. */
    493     if (0 ==
    494         strncmp (prefix,
    495                  label,
    496                  strlen (prefix)))
    497       label += strlen (prefix);
    498     GNUNET_assert (0 ==
    499                    json_array_append_new (labels,
    500                                           json_string (label)));
    501     GNUNET_free (rctx->labels[i]);
    502   }
    503   GNUNET_array_grow (rctx->labels,
    504                      rctx->labels_cnt,
    505                      0);
    506   chart = GNUNET_JSON_PACK (
    507     GNUNET_JSON_pack_string ("chart_name",
    508                              "Transaction counts"),
    509     GNUNET_JSON_pack_string ("y_label",
    510                              "Number of transactions"),
    511     GNUNET_JSON_pack_array_steal ("data_groups",
    512                                   rctx->data_groups),
    513     GNUNET_JSON_pack_array_steal ("labels",
    514                                   labels),
    515     GNUNET_JSON_pack_bool ("cumulative",
    516                            false));
    517   rctx->data_groups = NULL;
    518   GNUNET_assert (0 ==
    519                  json_array_append_new (charts,
    520                                         chart));
    521   return GNUNET_OK;
    522 }
    523 
    524 
    525 enum MHD_Result
    526 TMH_private_get_statistics_report_transactions (
    527   const struct TMH_RequestHandler *rh,
    528   struct MHD_Connection *connection,
    529   struct TMH_HandlerContext *hc)
    530 {
    531   struct ResponseContext *rctx = hc->ctx;
    532   struct TMH_MerchantInstance *mi = hc->instance;
    533   json_t *charts;
    534 
    535   if (NULL != rctx)
    536   {
    537     GNUNET_assert (GNUNET_YES != rctx->suspended);
    538     if (GNUNET_SYSERR == rctx->suspended)
    539       return MHD_NO;
    540     if (NULL == rctx->response)
    541     {
    542       GNUNET_break (0);
    543       return MHD_NO;
    544     }
    545     return MHD_queue_response (connection,
    546                                rctx->http_status,
    547                                rctx->response);
    548   }
    549   rctx = GNUNET_new (struct ResponseContext);
    550   rctx->hc = hc;
    551   rctx->now = GNUNET_TIME_timestamp_get ();
    552   hc->ctx = rctx;
    553   hc->cc = &free_rc;
    554   GNUNET_assert (NULL != mi);
    555 
    556   rctx->granularity = MHD_lookup_connection_value (connection,
    557                                                    MHD_GET_ARGUMENT_KIND,
    558                                                    "granularity");
    559   if (NULL == rctx->granularity)
    560   {
    561     rctx->granularity = "day";
    562     rctx->period = GNUNET_TIME_UNIT_DAYS;
    563     rctx->count = 95;
    564   }
    565   else
    566   {
    567     const struct
    568     {
    569       const char *name;
    570       struct GNUNET_TIME_Relative period;
    571       uint64_t default_counter;
    572     } map[] = {
    573       {
    574         .name = "second",
    575         .period = GNUNET_TIME_UNIT_SECONDS,
    576         .default_counter = 120,
    577       },
    578       {
    579         .name = "minute",
    580         .period = GNUNET_TIME_UNIT_MINUTES,
    581         .default_counter = 120,
    582       },
    583       {
    584         .name = "hour",
    585         .period = GNUNET_TIME_UNIT_HOURS,
    586         .default_counter = 48,
    587       },
    588       {
    589         .name = "day",
    590         .period = GNUNET_TIME_UNIT_DAYS,
    591         .default_counter = 95,
    592       },
    593       {
    594         .name = "week",
    595         .period = GNUNET_TIME_UNIT_WEEKS,
    596         .default_counter = 12,
    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_GONE,
    636         TALER_EC_MERCHANT_PRIVATE_GET_STATISTICS_REPORT_GRANULARITY_UNAVAILABLE,
    637         rctx->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 (TALER_MERCHANT_project_data (),
    732                                     TMH_cfg,
    733                                     false, /* remove on exit */
    734                                     "merchant",
    735                                     1, /* one document, length of "array"! */
    736                                     &doc,
    737                                     &pdf_cb,
    738                                     rctx);
    739         json_decref (root);
    740         if (NULL == rctx->tc)
    741         {
    742           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    743                       "Client requested PDF, but Typst is unavailable\n");
    744           return TALER_MHD_reply_with_error (
    745             connection,
    746             MHD_HTTP_NOT_IMPLEMENTED,
    747             TALER_EC_MERCHANT_GENERIC_NO_TYPST_OR_PDFTK,
    748             NULL);
    749         }
    750         GNUNET_CONTAINER_DLL_insert (rctx_head,
    751                                      rctx_tail,
    752                                      rctx);
    753         rctx->suspended = GNUNET_YES;
    754         MHD_suspend_connection (connection);
    755         return MHD_YES;
    756       }
    757     } /* end switch */
    758   }
    759   GNUNET_assert (0);
    760   return MHD_NO;
    761 }
    762 
    763 
    764 /* end of taler-merchant-httpd_get-private-statistics-report-transactions.c */