merchant

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

taler-merchant-httpd_get-private-orders.c (48455B)


      1 /*
      2   This file is part of TALER
      3   (C) 2019--2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU 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-orders.c
     18  * @brief implement GET /orders
     19  * @author Christian Grothoff
     20  *
     21  * FIXME-cleanup: consider introducing phases / state machine
     22  */
     23 #include "taler/platform.h"
     24 #include "taler-merchant-httpd_get-private-orders.h"
     25 #include <taler/taler_merchant_util.h>
     26 #include <taler/taler_json_lib.h>
     27 #include <taler/taler_dbevents.h>
     28 
     29 
     30 /**
     31  * Sensible bound on TALER_MERCHANTDB_OrderFilter.delta
     32  */
     33 #define MAX_DELTA 1024
     34 
     35 #define CSV_HEADER \
     36         "Order ID,Row,YYYY-MM-DD,HH:MM,Timestamp,Amount,Refund amount,Pending refund amount,Summary,Refundable,Paid\r\n"
     37 #define CSV_FOOTER "\r\n"
     38 
     39 #define XML_HEADER "<?xml version=\"1.0\"?>" \
     40         "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"" \
     41         " xmlns:c=\"urn:schemas-microsoft-com:office:component:spreadsheet\"" \
     42         " xmlns:html=\"http://www.w3.org/TR/REC-html40\"" \
     43         " xmlns:x2=\"http://schemas.microsoft.com/office/excel/2003/xml\"" \
     44         " xmlns:o=\"urn:schemas-microsoft-com:office:office\""        \
     45         " xmlns:x=\"urn:schemas-microsoft-com:office:excel\""         \
     46         " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">" \
     47         "<Styles>" \
     48         "<Style ss:ID=\"DateFormat\"><NumberFormat ss:Format=\"yyyy-mm-dd hh:mm:ss\"/></Style>" \
     49         "<Style ss:ID=\"Total\"><Font ss:Bold=\"1\"/></Style>" \
     50         "</Styles>\n" \
     51         "<Worksheet ss:Name=\"Orders\">\n"                            \
     52         "<Table>\n" \
     53         "<Row>\n" \
     54         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Order ID</Data></Cell>\n" \
     55         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Timestamp</Data></Cell>\n" \
     56         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Price</Data></Cell>\n" \
     57         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Refunded</Data></Cell>\n" \
     58         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Summary</Data></Cell>\n" \
     59         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Paid</Data></Cell>\n" \
     60         "</Row>\n"
     61 #define XML_FOOTER "</Table></Worksheet></Workbook>"
     62 
     63 
     64 /**
     65  * A pending GET /orders request.
     66  */
     67 struct TMH_PendingOrder
     68 {
     69 
     70   /**
     71    * Kept in a DLL.
     72    */
     73   struct TMH_PendingOrder *prev;
     74 
     75   /**
     76    * Kept in a DLL.
     77    */
     78   struct TMH_PendingOrder *next;
     79 
     80   /**
     81    * Which connection was suspended.
     82    */
     83   struct MHD_Connection *con;
     84 
     85   /**
     86    * Which instance is this client polling? This also defines
     87    * which DLL this struct is part of.
     88    */
     89   struct TMH_MerchantInstance *mi;
     90 
     91   /**
     92    * At what time does this request expire? If set in the future, we
     93    * may wait this long for a payment to arrive before responding.
     94    */
     95   struct GNUNET_TIME_Absolute long_poll_timeout;
     96 
     97   /**
     98    * Filter to apply.
     99    */
    100   struct TALER_MERCHANTDB_OrderFilter of;
    101 
    102   /**
    103    * The array of orders (used for JSON and PDF/Typst).
    104    */
    105   json_t *pa;
    106 
    107   /**
    108    * Running total of order amounts, for totals row in CSV/XML/PDF.
    109    * Initialised to zero on first order seen.
    110    */
    111   struct TALER_AmountSet total_amount;
    112 
    113   /**
    114    * Running total of granted refund amounts.
    115    * Initialised to zero on first paid order seen.
    116    */
    117   struct TALER_AmountSet total_refund_amount;
    118 
    119   /**
    120    * Running total of pending refund amounts.
    121    * Initialised to zero on first paid order seen.
    122    */
    123   struct TALER_AmountSet total_pending_refund_amount;
    124 
    125   /**
    126    * The name of the instance we are querying for.
    127    */
    128   const char *instance_id;
    129 
    130   /**
    131    * Alias of @a of.summary_filter, but with memory to be released (owner).
    132    */
    133   char *summary_filter;
    134 
    135   /**
    136    * The result after adding the orders (#TALER_EC_NONE for okay, anything else for an error).
    137    */
    138   enum TALER_ErrorCode result;
    139 
    140   /**
    141    * Is the structure in the DLL
    142    */
    143   bool in_dll;
    144 
    145   /**
    146    * Output format requested by the client.
    147    */
    148   enum
    149   {
    150     POF_JSON,
    151     POF_CSV,
    152     POF_XML,
    153     POF_PDF
    154   } format;
    155 
    156   /**
    157    * Buffer used when format is #POF_CSV.
    158    */
    159   struct GNUNET_Buffer csv;
    160 
    161   /**
    162    * Buffer used when format is #POF_XML.
    163    */
    164   struct GNUNET_Buffer xml;
    165 
    166   /**
    167    * Async context used to run Typst (for #POF_PDF).
    168    */
    169   struct TALER_MHD_TypstContext *tc;
    170 
    171   /**
    172    * Pre-built MHD response (used when #POF_PDF Typst is done).
    173    */
    174   struct MHD_Response *response;
    175 
    176   /**
    177    * Task to timeout pending order.
    178    */
    179   struct GNUNET_SCHEDULER_Task *order_timeout_task;
    180 
    181   /**
    182    * HTTP status to return with @e response.
    183    */
    184   unsigned int http_status;
    185 };
    186 
    187 
    188 /**
    189  * DLL head for requests suspended waiting for Typst.
    190  */
    191 static struct TMH_PendingOrder *pdf_head;
    192 
    193 /**
    194  * DLL tail for requests suspended waiting for Typst.
    195  */
    196 static struct TMH_PendingOrder *pdf_tail;
    197 
    198 
    199 void
    200 TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
    201 {
    202   struct TMH_PendingOrder *po;
    203 
    204   while (NULL != (po = mi->po_head))
    205   {
    206     GNUNET_assert (po->in_dll);
    207     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    208                                  mi->po_tail,
    209                                  po);
    210     MHD_resume_connection (po->con);
    211     po->in_dll = false;
    212   }
    213   if (NULL != mi->po_eh)
    214   {
    215     TMH_db->event_listen_cancel (mi->po_eh);
    216     mi->po_eh = NULL;
    217   }
    218 }
    219 
    220 
    221 void
    222 TMH_force_get_orders_resume_typst ()
    223 {
    224   struct TMH_PendingOrder *po;
    225 
    226   while (NULL != (po = pdf_head))
    227   {
    228     GNUNET_CONTAINER_DLL_remove (pdf_head,
    229                                  pdf_tail,
    230                                  po);
    231     MHD_resume_connection (po->con);
    232   }
    233 }
    234 
    235 
    236 /**
    237  * Task run to trigger timeouts on GET /orders requests with long polling.
    238  *
    239  * @param cls a `struct TMH_PendingOrder *`
    240  */
    241 static void
    242 order_timeout (void *cls)
    243 {
    244   struct TMH_PendingOrder *po = cls;
    245   struct TMH_MerchantInstance *mi = po->mi;
    246 
    247   po->order_timeout_task = NULL;
    248   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    249               "Resuming long polled job due to timeout\n");
    250   GNUNET_assert (po->in_dll);
    251   GNUNET_CONTAINER_DLL_remove (mi->po_head,
    252                                mi->po_tail,
    253                                po);
    254   po->in_dll = false;
    255   MHD_resume_connection (po->con);
    256   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    257 }
    258 
    259 
    260 /**
    261  * Cleanup our "context", where we stored the data
    262  * we are building for the response.
    263  *
    264  * @param ctx context to clean up, must be a `struct TMH_PendingOrder *`
    265  */
    266 static void
    267 cleanup (void *ctx)
    268 {
    269   struct TMH_PendingOrder *po = ctx;
    270 
    271   if (po->in_dll)
    272   {
    273     struct TMH_MerchantInstance *mi = po->mi;
    274 
    275     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    276                                  mi->po_tail,
    277                                  po);
    278     MHD_resume_connection (po->con);
    279   }
    280   if (NULL != po->order_timeout_task)
    281   {
    282     GNUNET_SCHEDULER_cancel (po->order_timeout_task);
    283     po->order_timeout_task = NULL;
    284   }
    285   json_decref (po->pa);
    286   TALER_amount_set_free (&po->total_amount);
    287   TALER_amount_set_free (&po->total_refund_amount);
    288   TALER_amount_set_free (&po->total_pending_refund_amount);
    289   GNUNET_free (po->summary_filter);
    290   switch (po->format)
    291   {
    292   case POF_JSON:
    293     break;
    294   case POF_CSV:
    295     GNUNET_buffer_clear (&po->csv);
    296     break;
    297   case POF_XML:
    298     GNUNET_buffer_clear (&po->xml);
    299     break;
    300   case POF_PDF:
    301     if (NULL != po->tc)
    302     {
    303       TALER_MHD_typst_cancel (po->tc);
    304       po->tc = NULL;
    305     }
    306     break;
    307   }
    308   if (NULL != po->response)
    309   {
    310     MHD_destroy_response (po->response);
    311     po->response = NULL;
    312   }
    313   GNUNET_free (po);
    314 }
    315 
    316 
    317 /**
    318  * Closure for #process_refunds_cb().
    319  */
    320 struct ProcessRefundsClosure
    321 {
    322   /**
    323    * Place where we accumulate the granted refunds.
    324    */
    325   struct TALER_Amount total_refund_amount;
    326 
    327   /**
    328    * Place where we accumulate the pending refunds.
    329    */
    330   struct TALER_Amount pending_refund_amount;
    331 
    332   /**
    333    * Set to an error code if something goes wrong.
    334    */
    335   enum TALER_ErrorCode ec;
    336 };
    337 
    338 
    339 /**
    340  * Function called with information about a refund.
    341  * It is responsible for summing up the refund amount.
    342  *
    343  * @param cls closure
    344  * @param refund_serial unique serial number of the refund
    345  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
    346  * @param coin_pub public coin from which the refund comes from
    347  * @param exchange_url URL of the exchange that issued @a coin_pub
    348  * @param rtransaction_id identificator of the refund
    349  * @param reason human-readable explanation of the refund
    350  * @param refund_amount refund amount which is being taken from @a coin_pub
    351  * @param pending true if the this refund was not yet processed by the wallet/exchange
    352  */
    353 static void
    354 process_refunds_cb (void *cls,
    355                     uint64_t refund_serial,
    356                     struct GNUNET_TIME_Timestamp timestamp,
    357                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
    358                     const char *exchange_url,
    359                     uint64_t rtransaction_id,
    360                     const char *reason,
    361                     const struct TALER_Amount *refund_amount,
    362                     bool pending)
    363 {
    364   struct ProcessRefundsClosure *prc = cls;
    365 
    366   if (GNUNET_OK !=
    367       TALER_amount_cmp_currency (&prc->total_refund_amount,
    368                                  refund_amount))
    369   {
    370     /* Database error, refunds in mixed currency in DB. Not OK! */
    371     prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
    372     GNUNET_break (0);
    373     return;
    374   }
    375   GNUNET_assert (0 <=
    376                  TALER_amount_add (&prc->total_refund_amount,
    377                                    &prc->total_refund_amount,
    378                                    refund_amount));
    379   if (pending)
    380     GNUNET_assert (0 <=
    381                    TALER_amount_add (&prc->pending_refund_amount,
    382                                      &prc->pending_refund_amount,
    383                                      refund_amount));
    384 }
    385 
    386 
    387 /**
    388  * Add one order entry to the running order-amount total in @a po.
    389  * Sets po->result to #TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT on overflow.
    390  *
    391  * @param[in,out] po pending order accumulator
    392  * @param amount the order amount to add
    393  */
    394 static void
    395 accumulate_total (struct TMH_PendingOrder *po,
    396                   const struct TALER_Amount *amount)
    397 {
    398   if (0 > TALER_amount_set_add (&po->total_amount,
    399                                 amount,
    400                                 NULL))
    401   {
    402     GNUNET_break (0);
    403     po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT;
    404   }
    405 }
    406 
    407 
    408 /**
    409  * Add refund amounts to the running refund totals in @a po.
    410  * Sets po->result to #TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT on overflow.
    411  * Only called for paid orders (where refund tracking is meaningful).
    412  *
    413  * @param[in,out] po pending order accumulator
    414  * @param refund granted refund amount for this order
    415  * @param pending pending (not-yet-processed) refund amount for this order
    416  */
    417 static void
    418 accumulate_refund_totals (struct TMH_PendingOrder *po,
    419                           const struct TALER_Amount *refund,
    420                           const struct TALER_Amount *pending)
    421 {
    422   if (TALER_EC_NONE != po->result)
    423     return;
    424   if (0 > TALER_amount_set_add (&po->total_refund_amount,
    425                                 refund,
    426                                 NULL))
    427   {
    428     GNUNET_break (0);
    429     po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT;
    430     return;
    431   }
    432   if (0 > TALER_amount_set_add (&po->total_pending_refund_amount,
    433                                 pending,
    434                                 NULL))
    435   {
    436     GNUNET_break (0);
    437     po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT;
    438   }
    439 }
    440 
    441 
    442 /**
    443  * Add order details to our response accumulator.
    444  *
    445  * @param cls some closure
    446  * @param orig_order_id the order this is about
    447  * @param order_serial serial ID of the order
    448  * @param creation_time when was the order created
    449  */
    450 static void
    451 add_order (void *cls,
    452            const char *orig_order_id,
    453            uint64_t order_serial,
    454            struct GNUNET_TIME_Timestamp creation_time)
    455 {
    456   struct TMH_PendingOrder *po = cls;
    457   json_t *contract_terms = NULL;
    458   struct TALER_PrivateContractHashP h_contract_terms;
    459   enum GNUNET_DB_QueryStatus qs;
    460   char *order_id = NULL;
    461   bool refundable = false;
    462   bool paid;
    463   bool wired;
    464   struct TALER_MERCHANT_Contract *contract = NULL;
    465   int16_t choice_index = -1;
    466   struct ProcessRefundsClosure prc = {
    467     .ec = TALER_EC_NONE
    468   };
    469   const struct TALER_Amount *amount;
    470   char amount_buf[128];
    471   char refund_buf[128];
    472   char pending_buf[128];
    473 
    474   /* Bail early if we already have an error */
    475   if (TALER_EC_NONE != po->result)
    476     return;
    477 
    478   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    479               "Adding order `%s' (%llu) to result set at instance `%s'\n",
    480               orig_order_id,
    481               (unsigned long long) order_serial,
    482               po->instance_id);
    483   qs = TMH_db->lookup_order_status_by_serial (TMH_db->cls,
    484                                               po->instance_id,
    485                                               order_serial,
    486                                               &order_id,
    487                                               &h_contract_terms,
    488                                               &paid);
    489   if (qs < 0)
    490   {
    491     GNUNET_break (0);
    492     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    493     return;
    494   }
    495   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    496   {
    497     /* Contract terms don't exist, so the order cannot be paid. */
    498     paid = false;
    499     if (NULL == orig_order_id)
    500     {
    501       /* Got a DB trigger about a new proposal, but it
    502          was already deleted again. Just ignore the event. */
    503       return;
    504     }
    505     order_id = GNUNET_strdup (orig_order_id);
    506   }
    507 
    508   {
    509     /* First try to find the order in the contracts */
    510     uint64_t os;
    511     bool session_matches;
    512 
    513     qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
    514                                          po->instance_id,
    515                                          order_id,
    516                                          NULL,
    517                                          &contract_terms,
    518                                          &os,
    519                                          &paid,
    520                                          &wired,
    521                                          &session_matches,
    522                                          NULL,
    523                                          &choice_index);
    524     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    525       GNUNET_break (os == order_serial);
    526   }
    527   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    528   {
    529     /* Might still be unclaimed, so try order table */
    530     struct TALER_MerchantPostDataHashP unused;
    531 
    532     paid = false;
    533     wired = false;
    534     qs = TMH_db->lookup_order (TMH_db->cls,
    535                                po->instance_id,
    536                                order_id,
    537                                NULL,
    538                                &unused,
    539                                &contract_terms);
    540   }
    541   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    542   {
    543     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    544                 "Order %llu disappeared during iteration. Skipping.\n",
    545                 (unsigned long long) order_serial);
    546     goto cleanup;
    547   }
    548   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    549   {
    550     GNUNET_break (0);
    551     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    552     goto cleanup;
    553   }
    554 
    555   contract = TALER_MERCHANT_contract_parse (contract_terms,
    556                                             true);
    557   if (NULL == contract)
    558   {
    559     GNUNET_break (0);
    560     po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
    561     goto cleanup;
    562   }
    563 
    564   if (paid)
    565   {
    566     const struct TALER_Amount *brutto;
    567 
    568     switch (contract->version)
    569     {
    570     case TALER_MERCHANT_CONTRACT_VERSION_0:
    571       brutto = &contract->details.v0.brutto;
    572       break;
    573     case TALER_MERCHANT_CONTRACT_VERSION_1:
    574       {
    575         struct TALER_MERCHANT_ContractChoice *choice
    576           = &contract->details.v1.choices[choice_index];
    577 
    578         GNUNET_assert (choice_index < contract->details.v1.choices_len);
    579         brutto = &choice->amount;
    580       }
    581       break;
    582     default:
    583       GNUNET_break (0);
    584       goto cleanup;
    585     }
    586     GNUNET_assert (GNUNET_OK ==
    587                    TALER_amount_set_zero (brutto->currency,
    588                                           &prc.total_refund_amount));
    589     GNUNET_assert (GNUNET_OK ==
    590                    TALER_amount_set_zero (brutto->currency,
    591                                           &prc.pending_refund_amount));
    592 
    593     qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
    594                                           po->instance_id,
    595                                           &h_contract_terms,
    596                                           &process_refunds_cb,
    597                                           &prc);
    598     if (0 > qs)
    599     {
    600       GNUNET_break (0);
    601       po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    602       goto cleanup;
    603     }
    604     if (TALER_EC_NONE != prc.ec)
    605     {
    606       GNUNET_break (0);
    607       po->result = prc.ec;
    608       goto cleanup;
    609     }
    610     if (0 > TALER_amount_cmp (&prc.total_refund_amount,
    611                               brutto) &&
    612         GNUNET_TIME_absolute_is_future (contract->refund_deadline.abs_time))
    613       refundable = true;
    614   }
    615 
    616   /* compute amount totals */
    617   amount = NULL;
    618   switch (contract->version)
    619   {
    620   case TALER_MERCHANT_CONTRACT_VERSION_0:
    621     {
    622       amount = &contract->details.v0.brutto;
    623 
    624       if (TALER_amount_is_zero (amount) &&
    625           (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
    626       {
    627         /* If we are actually filtering by wire status,
    628            and the order was over an amount of zero,
    629            do not return it as wire status is not
    630            exactly meaningful for orders over zero. */
    631         goto cleanup;
    632       }
    633 
    634       /* Accumulate order total */
    635       if (paid)
    636         accumulate_total (po,
    637                           amount);
    638       if (TALER_EC_NONE != po->result)
    639         goto cleanup;
    640       /* Accumulate refund totals (only meaningful for paid orders) */
    641       if (paid)
    642       {
    643         accumulate_refund_totals (po,
    644                                   &prc.total_refund_amount,
    645                                   &prc.pending_refund_amount);
    646         if (TALER_EC_NONE != po->result)
    647           goto cleanup;
    648       }
    649     }
    650     break;
    651   case TALER_MERCHANT_CONTRACT_VERSION_1:
    652     if (-1 == choice_index)
    653       choice_index = 0; /* default choice */
    654     GNUNET_assert (choice_index < contract->details.v1.choices_len);
    655     {
    656       struct TALER_MERCHANT_ContractChoice *choice
    657         = &contract->details.v1.choices[choice_index];
    658 
    659       amount = &choice->amount;
    660       /* Accumulate order total */
    661       accumulate_total (po,
    662                         amount);
    663       if (TALER_EC_NONE != po->result)
    664         goto cleanup;
    665       /* Accumulate refund totals (only meaningful for paid orders) */
    666       if (paid)
    667       {
    668         accumulate_refund_totals (po,
    669                                   &prc.total_refund_amount,
    670                                   &prc.pending_refund_amount);
    671         if (TALER_EC_NONE != po->result)
    672           goto cleanup;
    673       }
    674     }
    675   default:
    676     GNUNET_break (0);
    677     po->result = TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION;
    678     goto cleanup;
    679   }
    680 
    681   /* convert amounts to strings (needed for some formats) */
    682   /* FIXME: use currency formatting rules in the future
    683      instead of TALER_amount2s for human readability... */
    684   strcpy (amount_buf,
    685           TALER_amount2s (amount));
    686   if (paid)
    687     strcpy (refund_buf,
    688             TALER_amount2s (&prc.total_refund_amount));
    689   if (paid)
    690     strcpy (pending_buf,
    691             TALER_amount2s (&prc.pending_refund_amount));
    692 
    693   switch (po->format)
    694   {
    695   case POF_JSON:
    696   case POF_PDF:
    697     GNUNET_assert (
    698       0 ==
    699       json_array_append_new (
    700         po->pa,
    701         GNUNET_JSON_PACK (
    702           GNUNET_JSON_pack_string ("order_id",
    703                                    contract->order_id),
    704           GNUNET_JSON_pack_uint64 ("row_id",
    705                                    order_serial),
    706           GNUNET_JSON_pack_timestamp ("timestamp",
    707                                       creation_time),
    708           TALER_JSON_pack_amount ("amount",
    709                                   amount),
    710           GNUNET_JSON_pack_allow_null (
    711             TALER_JSON_pack_amount (
    712               "refund_amount",
    713               paid
    714                 ? &prc.total_refund_amount
    715                 : NULL)),
    716           GNUNET_JSON_pack_allow_null (
    717             TALER_JSON_pack_amount (
    718               "pending_refund_amount",
    719               paid
    720                 ? &prc.pending_refund_amount
    721                 : NULL)),
    722           GNUNET_JSON_pack_string ("summary",
    723                                    contract->summary),
    724           GNUNET_JSON_pack_bool ("refundable",
    725                                  refundable),
    726           GNUNET_JSON_pack_bool ("paid",
    727                                  paid))));
    728     break;
    729   case POF_CSV:
    730     {
    731       size_t len = strlen (contract->summary);
    732       size_t wpos = 0;
    733       char *esummary;
    734       struct tm *tm;
    735       time_t t;
    736 
    737       /* Escape 'summary' to double '"' as per RFC 4180, 2.7. */
    738       esummary = GNUNET_malloc (2 * len + 1);
    739       for (size_t off = 0; off<len; off++)
    740       {
    741         if ('"' == contract->summary[off])
    742           esummary[wpos++] = '"';
    743         esummary[wpos++] = contract->summary[off];
    744       }
    745       t = GNUNET_TIME_timestamp_to_s (creation_time);
    746       tm = localtime (&t);
    747       GNUNET_buffer_write_fstr (
    748         &po->csv,
    749         "%s,%llu,%04u-%02u-%02u,%02u:%02u (%s),%llu,%s,%s,%s,\"%s\",%s,%s\r\n",
    750         contract->order_id,
    751         (unsigned long long) order_serial,
    752         tm->tm_year + 1900,
    753         tm->tm_mon + 1,
    754         tm->tm_mday,
    755         tm->tm_hour,
    756         tm->tm_min,
    757         tm->tm_zone,
    758         (unsigned long long) t,
    759         amount_buf,
    760         paid ? refund_buf : "",
    761         paid ? pending_buf : "",
    762         esummary,
    763         refundable ? "yes" : "no",
    764         paid ? "yes" : "no");
    765       GNUNET_free (esummary);
    766       break;
    767     }
    768   case POF_XML:
    769     {
    770       char *esummary = TALER_escape_xml (contract->summary);
    771       char creation_time_s[128];
    772       const struct tm *tm;
    773       time_t tt;
    774 
    775       tt = (time_t) GNUNET_TIME_timestamp_to_s (creation_time);
    776       tm = gmtime (&tt);
    777       strftime (creation_time_s,
    778                 sizeof (creation_time_s),
    779                 "%Y-%m-%dT%H:%M:%S",
    780                 tm);
    781       GNUNET_buffer_write_fstr (
    782         &po->xml,
    783         "<Row>"
    784         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    785         "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"DateTime\">%s</Data></Cell>"
    786         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    787         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    788         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    789         "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>"
    790         "</Row>\n",
    791         contract->order_id,
    792         creation_time_s,
    793         amount_buf,
    794         paid ? refund_buf : "",
    795         NULL != esummary ? esummary : "",
    796         paid ? "TRUE" : "FALSE",
    797         paid ? "1" : "0");
    798       GNUNET_free (esummary);
    799     }
    800     break;
    801   }    /* end switch po->format */
    802 
    803 cleanup:
    804   json_decref (contract_terms);
    805   GNUNET_free (order_id);
    806   if (NULL != contract)
    807   {
    808     TALER_MERCHANT_contract_free (contract);
    809     contract = NULL;
    810   }
    811 }
    812 
    813 
    814 /**
    815  * We have received a trigger from the database
    816  * that we should (possibly) resume some requests.
    817  *
    818  * @param cls a `struct TMH_MerchantInstance`
    819  * @param extra a `struct TMH_OrderChangeEventP`
    820  * @param extra_size number of bytes in @a extra
    821  */
    822 static void
    823 resume_by_event (void *cls,
    824                  const void *extra,
    825                  size_t extra_size)
    826 {
    827   struct TMH_MerchantInstance *mi = cls;
    828   const struct TMH_OrderChangeEventDetailsP *oce = extra;
    829   struct TMH_PendingOrder *pn;
    830   enum TMH_OrderStateFlags osf;
    831   uint64_t order_serial_id;
    832   struct GNUNET_TIME_Timestamp date;
    833 
    834   if (sizeof (*oce) != extra_size)
    835   {
    836     GNUNET_break (0);
    837     return;
    838   }
    839   osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
    840   order_serial_id = GNUNET_ntohll (oce->order_serial_id);
    841   date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
    842   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    843               "Received notification about order %llu\n",
    844               (unsigned long long) order_serial_id);
    845   for (struct TMH_PendingOrder *po = mi->po_head;
    846        NULL != po;
    847        po = pn)
    848   {
    849     pn = po->next;
    850     if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
    851                (0 != (osf & TMH_OSF_PAID))) ||
    852               (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
    853             ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
    854                (0 != (osf & TMH_OSF_REFUNDED))) ||
    855               (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
    856             ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
    857                (0 != (osf & TMH_OSF_WIRED))) ||
    858               (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
    859     {
    860       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    861                   "Client %p waits on different order type\n",
    862                   po);
    863       continue;
    864     }
    865     if (po->of.delta > 0)
    866     {
    867       if (order_serial_id < po->of.start_row)
    868       {
    869         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    870                     "Client %p waits on different order row\n",
    871                     po);
    872         continue;
    873       }
    874       if (GNUNET_TIME_timestamp_cmp (date,
    875                                      <,
    876                                      po->of.date))
    877       {
    878         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    879                     "Client %p waits on different order date\n",
    880                     po);
    881         continue;
    882       }
    883       po->of.delta--;
    884     }
    885     else
    886     {
    887       if (order_serial_id > po->of.start_row)
    888       {
    889         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    890                     "Client %p waits on different order row\n",
    891                     po);
    892         continue;
    893       }
    894       if (GNUNET_TIME_timestamp_cmp (date,
    895                                      >,
    896                                      po->of.date))
    897       {
    898         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    899                     "Client %p waits on different order date\n",
    900                     po);
    901         continue;
    902       }
    903       po->of.delta++;
    904     }
    905     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    906                 "Waking up client %p!\n",
    907                 po);
    908     add_order (po,
    909                NULL,
    910                order_serial_id,
    911                date);
    912     GNUNET_assert (po->in_dll);
    913     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    914                                  mi->po_tail,
    915                                  po);
    916     po->in_dll = false;
    917     MHD_resume_connection (po->con);
    918     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    919   }
    920   if (NULL == mi->po_head)
    921   {
    922     TMH_db->event_listen_cancel (mi->po_eh);
    923     mi->po_eh = NULL;
    924   }
    925 }
    926 
    927 
    928 /**
    929  * There has been a change or addition of a new @a order_id.  Wake up
    930  * long-polling clients that may have been waiting for this event.
    931  *
    932  * @param mi the instance where the order changed
    933  * @param osf order state flags
    934  * @param date execution date of the order
    935  * @param order_serial_id serial ID of the order in the database
    936  */
    937 void
    938 TMH_notify_order_change (struct TMH_MerchantInstance *mi,
    939                          enum TMH_OrderStateFlags osf,
    940                          struct GNUNET_TIME_Timestamp date,
    941                          uint64_t order_serial_id)
    942 {
    943   struct TMH_OrderChangeEventDetailsP oce = {
    944     .order_serial_id = GNUNET_htonll (order_serial_id),
    945     .execution_date = GNUNET_TIME_timestamp_hton (date),
    946     .order_state = htonl (osf)
    947   };
    948   struct TMH_OrderChangeEventP eh = {
    949     .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
    950     .header.size = htons (sizeof (eh)),
    951     .merchant_pub = mi->merchant_pub
    952   };
    953 
    954   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    955               "Notifying clients of new order %llu at %s\n",
    956               (unsigned long long) order_serial_id,
    957               TALER_B2S (&mi->merchant_pub));
    958   TMH_db->event_notify (TMH_db->cls,
    959                         &eh.header,
    960                         &oce,
    961                         sizeof (oce));
    962 }
    963 
    964 
    965 /**
    966  * Transforms an (untrusted) input filter into a Postgresql LIKE filter.
    967  * Escapes "%" and "_" in the @a input and adds "%" at the beginning
    968  * and the end to turn the @a input into a suitable Postgresql argument.
    969  *
    970  * @param input text to turn into a substring match expression, or NULL
    971  * @return NULL if @a input was NULL, otherwise transformed @a input
    972  */
    973 static char *
    974 tr (const char *input)
    975 {
    976   char *out;
    977   size_t slen;
    978   size_t wpos;
    979 
    980   if (NULL == input)
    981     return NULL;
    982   slen = strlen (input);
    983   out = GNUNET_malloc (slen * 2 + 3);
    984   wpos = 0;
    985   out[wpos++] = '%';
    986   for (size_t i = 0; i<slen; i++)
    987   {
    988     char c = input[i];
    989 
    990     if ( (c == '%') ||
    991          (c == '_') )
    992       out[wpos++] = '\\';
    993     out[wpos++] = c;
    994   }
    995   out[wpos++] = '%';
    996   GNUNET_assert (wpos < slen * 2 + 3);
    997   return out;
    998 }
    999 
   1000 
   1001 /**
   1002  * Function called with the result of a #TALER_MHD_typst() operation.
   1003  *
   1004  * @param cls closure, a `struct TMH_PendingOrder *`
   1005  * @param tr result of the operation
   1006  */
   1007 static void
   1008 pdf_cb (void *cls,
   1009         const struct TALER_MHD_TypstResponse *tr)
   1010 {
   1011   struct TMH_PendingOrder *po = cls;
   1012 
   1013   po->tc = NULL;
   1014   GNUNET_CONTAINER_DLL_remove (pdf_head,
   1015                                pdf_tail,
   1016                                po);
   1017   if (TALER_EC_NONE != tr->ec)
   1018   {
   1019     po->http_status
   1020       = TALER_ErrorCode_get_http_status (tr->ec);
   1021     po->response
   1022       = TALER_MHD_make_error (tr->ec,
   1023                               tr->details.hint);
   1024   }
   1025   else
   1026   {
   1027     po->http_status = MHD_HTTP_OK;
   1028     po->response = TALER_MHD_response_from_pdf_file (tr->details.filename);
   1029   }
   1030   MHD_resume_connection (po->con);
   1031   TALER_MHD_daemon_trigger ();
   1032 }
   1033 
   1034 
   1035 /**
   1036  * Build the final response for a completed (non-long-poll) request and
   1037  * queue it on @a connection.
   1038  *
   1039  * Handles all formats (JSON, CSV, XML, PDF).  For PDF this may suspend
   1040  * the connection while Typst runs asynchronously; in that case the caller
   1041  * must return #MHD_YES immediately.
   1042  *
   1043  * @param po the pending order state (already fully populated)
   1044  * @param connection the MHD connection
   1045  * @param mi the merchant instance
   1046  * @return MHD result code
   1047  */
   1048 static MHD_RESULT
   1049 reply_orders (struct TMH_PendingOrder *po,
   1050               struct MHD_Connection *connection,
   1051               struct TMH_MerchantInstance *mi)
   1052 {
   1053   char total_buf[128];
   1054   char refund_buf[128];
   1055   char pending_buf[128];
   1056 
   1057   switch (po->format)
   1058   {
   1059   case POF_JSON:
   1060     return TALER_MHD_REPLY_JSON_PACK (
   1061       connection,
   1062       MHD_HTTP_OK,
   1063       GNUNET_JSON_pack_array_incref ("orders",
   1064                                      po->pa));
   1065   case POF_CSV:
   1066     {
   1067       struct MHD_Response *resp;
   1068       MHD_RESULT mret;
   1069 
   1070       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
   1071       {
   1072         struct TALER_Amount *tai = &po->total_amount.taa[i];
   1073         const struct TALER_Amount *r;
   1074 
   1075         strcpy (total_buf,
   1076                 TALER_amount2s (tai));
   1077         r = TALER_amount_set_find (tai->currency,
   1078                                    &po->total_refund_amount);
   1079         strcpy (refund_buf,
   1080                 TALER_amount2s (r));
   1081         r = TALER_amount_set_find (tai->currency,
   1082                                    &po->total_pending_refund_amount);
   1083         strcpy (pending_buf,
   1084                 TALER_amount2s (r));
   1085 
   1086         GNUNET_buffer_write_fstr (
   1087           &po->csv,
   1088           "Total (paid %s only),,,,%s,%s,%s,,,\r\n",
   1089           tai->currency,
   1090           total_buf,
   1091           refund_buf,
   1092           pending_buf);
   1093       }
   1094       GNUNET_buffer_write_str (&po->csv,
   1095                                CSV_FOOTER);
   1096       resp = MHD_create_response_from_buffer (po->csv.position,
   1097                                               po->csv.mem,
   1098                                               MHD_RESPMEM_MUST_COPY);
   1099       TALER_MHD_add_global_headers (resp,
   1100                                     false);
   1101       GNUNET_break (MHD_YES ==
   1102                     MHD_add_response_header (resp,
   1103                                              MHD_HTTP_HEADER_CONTENT_TYPE,
   1104                                              "text/csv"));
   1105       mret = MHD_queue_response (connection,
   1106                                  MHD_HTTP_OK,
   1107                                  resp);
   1108       MHD_destroy_response (resp);
   1109       return mret;
   1110     }
   1111   case POF_XML:
   1112     {
   1113       struct MHD_Response *resp;
   1114       MHD_RESULT mret;
   1115 
   1116       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
   1117       {
   1118         struct TALER_Amount *tai = &po->total_amount.taa[i];
   1119         const struct TALER_Amount *r;
   1120 
   1121         strcpy (total_buf,
   1122                 TALER_amount2s (tai));
   1123         r = TALER_amount_set_find (tai->currency,
   1124                                    &po->total_refund_amount);
   1125         strcpy (refund_buf,
   1126                 TALER_amount2s (r));
   1127         r = TALER_amount_set_find (tai->currency,
   1128                                    &po->total_pending_refund_amount);
   1129         strcpy (pending_buf,
   1130                 TALER_amount2s (r));
   1131 
   1132         /* Append totals row with paid and refunded amount columns */
   1133         GNUNET_buffer_write_fstr (
   1134           &po->xml,
   1135           "<Row>"
   1136           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">Total (paid %s only)</Data></Cell>"
   1137           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
   1138           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>"
   1139           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>"
   1140           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
   1141           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
   1142           "</Row>\n",
   1143           tai->currency,
   1144           total_buf,
   1145           refund_buf);
   1146       }
   1147       GNUNET_buffer_write_str (&po->xml,
   1148                                XML_FOOTER);
   1149       resp = MHD_create_response_from_buffer (po->xml.position,
   1150                                               po->xml.mem,
   1151                                               MHD_RESPMEM_MUST_COPY);
   1152       TALER_MHD_add_global_headers (resp,
   1153                                     false);
   1154       GNUNET_break (MHD_YES ==
   1155                     MHD_add_response_header (resp,
   1156                                              MHD_HTTP_HEADER_CONTENT_TYPE,
   1157                                              "application/vnd.ms-excel"));
   1158       mret = MHD_queue_response (connection,
   1159                                  MHD_HTTP_OK,
   1160                                  resp);
   1161       MHD_destroy_response (resp);
   1162       return mret;
   1163     }
   1164   case POF_PDF:
   1165     {
   1166       /* Build the JSON document for Typst, passing all totals */
   1167       json_t *root;
   1168       struct TALER_MHD_TypstDocument doc;
   1169       json_t *ta = json_array ();
   1170       json_t *ra = json_array ();
   1171       json_t *pa = json_array ();
   1172 
   1173       GNUNET_assert (NULL != ta);
   1174       GNUNET_assert (NULL != ra);
   1175       GNUNET_assert (NULL != pa);
   1176       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
   1177       {
   1178         struct TALER_Amount *tai = &po->total_amount.taa[i];
   1179         const struct TALER_Amount *r;
   1180 
   1181         GNUNET_assert (0 ==
   1182                        json_array_append_new (ta,
   1183                                               TALER_JSON_from_amount (tai)));
   1184         r = TALER_amount_set_find (tai->currency,
   1185                                    &po->total_refund_amount);
   1186         GNUNET_assert (0 ==
   1187                        json_array_append_new (ra,
   1188                                               TALER_JSON_from_amount (r)));
   1189         r = TALER_amount_set_find (tai->currency,
   1190                                    &po->total_pending_refund_amount);
   1191         GNUNET_assert (0 ==
   1192                        json_array_append_new (pa,
   1193                                               TALER_JSON_from_amount (r)));
   1194       }
   1195       root = GNUNET_JSON_PACK (
   1196         GNUNET_JSON_pack_string ("business_name",
   1197                                  mi->settings.name),
   1198         GNUNET_JSON_pack_array_incref ("orders",
   1199                                        po->pa),
   1200         GNUNET_JSON_pack_array_steal ("total_amounts",
   1201                                       ta),
   1202         GNUNET_JSON_pack_array_steal ("total_refund_amounts",
   1203                                       ra),
   1204         GNUNET_JSON_pack_array_steal ("total_pending_refund_amounts",
   1205                                       pa));
   1206       doc.form_name = "orders";
   1207       doc.form_version = "0.0.0";
   1208       doc.data = root;
   1209 
   1210       po->tc = TALER_MHD_typst (TMH_cfg,
   1211                                 false,  /* remove on exit */
   1212                                 "merchant",
   1213                                 1,     /* one document */
   1214                                 &doc,
   1215                                 &pdf_cb,
   1216                                 po);
   1217       json_decref (root);
   1218       if (NULL == po->tc)
   1219       {
   1220         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1221                     "Client requested PDF, but Typst is unavailable\n");
   1222         return TALER_MHD_reply_with_error (
   1223           connection,
   1224           MHD_HTTP_NOT_IMPLEMENTED,
   1225           TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK,
   1226           NULL);
   1227       }
   1228       GNUNET_CONTAINER_DLL_insert (pdf_head,
   1229                                    pdf_tail,
   1230                                    po);
   1231       MHD_suspend_connection (connection);
   1232       return MHD_YES;
   1233     }
   1234   } /* end switch */
   1235   GNUNET_assert (0);
   1236   return MHD_NO;
   1237 }
   1238 
   1239 
   1240 /**
   1241  * Handle a GET "/orders" request.
   1242  *
   1243  * @param rh context of the handler
   1244  * @param connection the MHD connection to handle
   1245  * @param[in,out] hc context with further information about the request
   1246  * @return MHD result code
   1247  */
   1248 MHD_RESULT
   1249 TMH_private_get_orders (const struct TMH_RequestHandler *rh,
   1250                         struct MHD_Connection *connection,
   1251                         struct TMH_HandlerContext *hc)
   1252 {
   1253   struct TMH_PendingOrder *po = hc->ctx;
   1254   struct TMH_MerchantInstance *mi = hc->instance;
   1255   enum GNUNET_DB_QueryStatus qs;
   1256 
   1257   if (NULL != po)
   1258   {
   1259     if (TALER_EC_NONE != po->result)
   1260     {
   1261       /* Resumed from long-polling with error */
   1262       GNUNET_break (0);
   1263       return TALER_MHD_reply_with_error (connection,
   1264                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
   1265                                          po->result,
   1266                                          NULL);
   1267     }
   1268     if (POF_PDF == po->format)
   1269     {
   1270       /* resumed from long-polling or from Typst PDF generation */
   1271       /* We really must have a response in this case */
   1272       if (NULL == po->response)
   1273       {
   1274         GNUNET_break (0);
   1275         return MHD_NO;
   1276       }
   1277       return MHD_queue_response (connection,
   1278                                  po->http_status,
   1279                                  po->response);
   1280     }
   1281     return reply_orders (po,
   1282                          connection,
   1283                          mi);
   1284   }
   1285   po = GNUNET_new (struct TMH_PendingOrder);
   1286   hc->ctx = po;
   1287   hc->cc = &cleanup;
   1288   po->con = connection;
   1289   po->pa = json_array ();
   1290   GNUNET_assert (NULL != po->pa);
   1291   po->instance_id = mi->settings.id;
   1292   po->mi = mi;
   1293 
   1294   /* Determine desired output format from Accept header */
   1295   {
   1296     const char *mime;
   1297 
   1298     mime = MHD_lookup_connection_value (connection,
   1299                                         MHD_HEADER_KIND,
   1300                                         MHD_HTTP_HEADER_ACCEPT);
   1301     if (NULL == mime)
   1302       mime = "application/json";
   1303     if (0 == strcmp (mime,
   1304                      "*/*"))
   1305       mime = "application/json";
   1306     if (0 == strcmp (mime,
   1307                      "application/json"))
   1308     {
   1309       po->format = POF_JSON;
   1310     }
   1311     else if (0 == strcmp (mime,
   1312                           "text/csv"))
   1313     {
   1314       po->format = POF_CSV;
   1315       GNUNET_buffer_write_str (&po->csv,
   1316                                CSV_HEADER);
   1317     }
   1318     else if (0 == strcmp (mime,
   1319                           "application/vnd.ms-excel"))
   1320     {
   1321       po->format = POF_XML;
   1322       GNUNET_buffer_write_str (&po->xml,
   1323                                XML_HEADER);
   1324     }
   1325     else if (0 == strcmp (mime,
   1326                           "application/pdf"))
   1327     {
   1328       po->format = POF_PDF;
   1329     }
   1330     else
   1331     {
   1332       GNUNET_break_op (0);
   1333       return TALER_MHD_REPLY_JSON_PACK (
   1334         connection,
   1335         MHD_HTTP_NOT_ACCEPTABLE,
   1336         GNUNET_JSON_pack_string ("hint",
   1337                                  mime));
   1338     }
   1339   }
   1340 
   1341   if (! (TALER_MHD_arg_to_yna (connection,
   1342                                "paid",
   1343                                TALER_EXCHANGE_YNA_ALL,
   1344                                &po->of.paid)) )
   1345   {
   1346     GNUNET_break_op (0);
   1347     return TALER_MHD_reply_with_error (connection,
   1348                                        MHD_HTTP_BAD_REQUEST,
   1349                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1350                                        "paid");
   1351   }
   1352   if (! (TALER_MHD_arg_to_yna (connection,
   1353                                "refunded",
   1354                                TALER_EXCHANGE_YNA_ALL,
   1355                                &po->of.refunded)) )
   1356   {
   1357     GNUNET_break_op (0);
   1358     return TALER_MHD_reply_with_error (connection,
   1359                                        MHD_HTTP_BAD_REQUEST,
   1360                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1361                                        "refunded");
   1362   }
   1363   if (! (TALER_MHD_arg_to_yna (connection,
   1364                                "wired",
   1365                                TALER_EXCHANGE_YNA_ALL,
   1366                                &po->of.wired)) )
   1367   {
   1368     GNUNET_break_op (0);
   1369     return TALER_MHD_reply_with_error (connection,
   1370                                        MHD_HTTP_BAD_REQUEST,
   1371                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1372                                        "wired");
   1373   }
   1374   po->of.delta = -20;
   1375   /* deprecated in protocol v12 */
   1376   TALER_MHD_parse_request_snumber (connection,
   1377                                    "delta",
   1378                                    &po->of.delta);
   1379   /* since protocol v12 */
   1380   TALER_MHD_parse_request_snumber (connection,
   1381                                    "limit",
   1382                                    &po->of.delta);
   1383   if ( (-MAX_DELTA > po->of.delta) ||
   1384        (po->of.delta > MAX_DELTA) )
   1385   {
   1386     GNUNET_break_op (0);
   1387     return TALER_MHD_reply_with_error (connection,
   1388                                        MHD_HTTP_BAD_REQUEST,
   1389                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1390                                        "limit");
   1391   }
   1392   {
   1393     const char *date_s_str;
   1394 
   1395     date_s_str = MHD_lookup_connection_value (connection,
   1396                                               MHD_GET_ARGUMENT_KIND,
   1397                                               "date_s");
   1398     if (NULL == date_s_str)
   1399     {
   1400       if (po->of.delta > 0)
   1401         po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
   1402       else
   1403         po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
   1404     }
   1405     else
   1406     {
   1407       char dummy;
   1408       unsigned long long ll;
   1409 
   1410       if (1 !=
   1411           sscanf (date_s_str,
   1412                   "%llu%c",
   1413                   &ll,
   1414                   &dummy))
   1415       {
   1416         GNUNET_break_op (0);
   1417         return TALER_MHD_reply_with_error (connection,
   1418                                            MHD_HTTP_BAD_REQUEST,
   1419                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1420                                            "date_s");
   1421       }
   1422 
   1423       po->of.date = GNUNET_TIME_absolute_to_timestamp (
   1424         GNUNET_TIME_absolute_from_s (ll));
   1425       if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
   1426       {
   1427         GNUNET_break_op (0);
   1428         return TALER_MHD_reply_with_error (connection,
   1429                                            MHD_HTTP_BAD_REQUEST,
   1430                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1431                                            "date_s");
   1432       }
   1433     }
   1434   }
   1435   if (po->of.delta > 0)
   1436   {
   1437     struct GNUNET_TIME_Relative duration
   1438       = GNUNET_TIME_UNIT_FOREVER_REL;
   1439     struct GNUNET_TIME_Absolute cut_off;
   1440 
   1441     TALER_MHD_parse_request_rel_time (connection,
   1442                                       "max_age",
   1443                                       &duration);
   1444     cut_off = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
   1445                                              duration);
   1446     po->of.date = GNUNET_TIME_timestamp_max (
   1447       po->of.date,
   1448       GNUNET_TIME_absolute_to_timestamp (cut_off));
   1449   }
   1450   if (po->of.delta > 0)
   1451     po->of.start_row = 0;
   1452   else
   1453     po->of.start_row = INT64_MAX;
   1454   /* deprecated in protocol v12 */
   1455   TALER_MHD_parse_request_number (connection,
   1456                                   "start",
   1457                                   &po->of.start_row);
   1458   /* since protocol v12 */
   1459   TALER_MHD_parse_request_number (connection,
   1460                                   "offset",
   1461                                   &po->of.start_row);
   1462   if (INT64_MAX < po->of.start_row)
   1463   {
   1464     GNUNET_break_op (0);
   1465     return TALER_MHD_reply_with_error (connection,
   1466                                        MHD_HTTP_BAD_REQUEST,
   1467                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1468                                        "offset");
   1469   }
   1470   po->summary_filter = tr (MHD_lookup_connection_value (connection,
   1471                                                         MHD_GET_ARGUMENT_KIND,
   1472                                                         "summary_filter"));
   1473   po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
   1474   po->of.session_id
   1475     = MHD_lookup_connection_value (connection,
   1476                                    MHD_GET_ARGUMENT_KIND,
   1477                                    "session_id");
   1478   po->of.fulfillment_url
   1479     = MHD_lookup_connection_value (connection,
   1480                                    MHD_GET_ARGUMENT_KIND,
   1481                                    "fulfillment_url");
   1482   TALER_MHD_parse_request_timeout (connection,
   1483                                    &po->long_poll_timeout);
   1484   if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
   1485   {
   1486     GNUNET_break_op (0);
   1487     return TALER_MHD_reply_with_error (connection,
   1488                                        MHD_HTTP_BAD_REQUEST,
   1489                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1490                                        "timeout_ms");
   1491   }
   1492   if ( (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) &&
   1493        (NULL == mi->po_eh) )
   1494   {
   1495     struct TMH_OrderChangeEventP change_eh = {
   1496       .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
   1497       .header.size = htons (sizeof (change_eh)),
   1498       .merchant_pub = mi->merchant_pub
   1499     };
   1500 
   1501     mi->po_eh = TMH_db->event_listen (TMH_db->cls,
   1502                                       &change_eh.header,
   1503                                       GNUNET_TIME_UNIT_FOREVER_REL,
   1504                                       &resume_by_event,
   1505                                       mi);
   1506   }
   1507 
   1508   po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
   1509 
   1510   qs = TMH_db->lookup_orders (TMH_db->cls,
   1511                               po->instance_id,
   1512                               &po->of,
   1513                               &add_order,
   1514                               po);
   1515   if (0 > qs)
   1516   {
   1517     GNUNET_break (0);
   1518     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
   1519   }
   1520   if (TALER_EC_NONE != po->result)
   1521   {
   1522     GNUNET_break (0);
   1523     return TALER_MHD_reply_with_error (connection,
   1524                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
   1525                                        po->result,
   1526                                        NULL);
   1527   }
   1528   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
   1529        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
   1530   {
   1531     GNUNET_assert (NULL == po->order_timeout_task);
   1532     po->order_timeout_task
   1533       = GNUNET_SCHEDULER_add_at (po->long_poll_timeout,
   1534                                  &order_timeout,
   1535                                  po);
   1536     GNUNET_CONTAINER_DLL_insert (mi->po_head,
   1537                                  mi->po_tail,
   1538                                  po);
   1539     po->in_dll = true;
   1540     MHD_suspend_connection (connection);
   1541     return MHD_YES;
   1542   }
   1543   return reply_orders (po,
   1544                        connection,
   1545                        mi);
   1546 }
   1547 
   1548 
   1549 /* end of taler-merchant-httpd_get-private-orders.c */