merchant

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

taler-merchant-httpd_private-get-orders.c (31849B)


      1 /*
      2   This file is part of TALER
      3   (C) 2019--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_private-get-orders.c
     18  * @brief implement GET /orders
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "taler-merchant-httpd_private-get-orders.h"
     23 #include <taler_merchant_util.h>
     24 #include <taler/taler_json_lib.h>
     25 #include <taler/taler_dbevents.h>
     26 
     27 
     28 /**
     29  * Sensible bound on TALER_MERCHANTDB_OrderFilter.delta
     30  */
     31 #define MAX_DELTA 1024
     32 
     33 
     34 /**
     35  * A pending GET /orders request.
     36  */
     37 struct TMH_PendingOrder
     38 {
     39 
     40   /**
     41    * Kept in a DLL.
     42    */
     43   struct TMH_PendingOrder *prev;
     44 
     45   /**
     46    * Kept in a DLL.
     47    */
     48   struct TMH_PendingOrder *next;
     49 
     50   /**
     51    * Which connection was suspended.
     52    */
     53   struct MHD_Connection *con;
     54 
     55   /**
     56    * Associated heap node.
     57    */
     58   struct GNUNET_CONTAINER_HeapNode *hn;
     59 
     60   /**
     61    * Which instance is this client polling? This also defines
     62    * which DLL this struct is part of.
     63    */
     64   struct TMH_MerchantInstance *mi;
     65 
     66   /**
     67    * At what time does this request expire? If set in the future, we
     68    * may wait this long for a payment to arrive before responding.
     69    */
     70   struct GNUNET_TIME_Absolute long_poll_timeout;
     71 
     72   /**
     73    * Filter to apply.
     74    */
     75   struct TALER_MERCHANTDB_OrderFilter of;
     76 
     77   /**
     78    * The array of orders.
     79    */
     80   json_t *pa;
     81 
     82   /**
     83    * The name of the instance we are querying for.
     84    */
     85   const char *instance_id;
     86 
     87   /**
     88    * Alias of @a of.summary_filter, but with memory to be released (owner).
     89    */
     90   char *summary_filter;
     91 
     92   /**
     93    * The result after adding the orders (#TALER_EC_NONE for okay, anything else for an error).
     94    */
     95   enum TALER_ErrorCode result;
     96 
     97   /**
     98    * Is the structure in the DLL
     99    */
    100   bool in_dll;
    101 };
    102 
    103 
    104 /**
    105  * Task to timeout pending orders.
    106  */
    107 static struct GNUNET_SCHEDULER_Task *order_timeout_task;
    108 
    109 /**
    110  * Heap for orders in long polling awaiting timeout.
    111  */
    112 static struct GNUNET_CONTAINER_Heap *order_timeout_heap;
    113 
    114 
    115 /**
    116  * We are shutting down (or an instance is being deleted), force resume of all
    117  * GET /orders requests.
    118  *
    119  * @param mi instance to force resuming for
    120  */
    121 void
    122 TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
    123 {
    124   struct TMH_PendingOrder *po;
    125 
    126   while (NULL != (po = mi->po_head))
    127   {
    128     GNUNET_assert (po->in_dll);
    129     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    130                                  mi->po_tail,
    131                                  po);
    132     GNUNET_assert (po ==
    133                    GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
    134     MHD_resume_connection (po->con);
    135     po->in_dll = false;
    136   }
    137   if (NULL != mi->po_eh)
    138   {
    139     TMH_db->event_listen_cancel (mi->po_eh);
    140     mi->po_eh = NULL;
    141   }
    142   if (NULL != order_timeout_task)
    143   {
    144     GNUNET_SCHEDULER_cancel (order_timeout_task);
    145     order_timeout_task = NULL;
    146   }
    147   if (NULL != order_timeout_heap)
    148   {
    149     GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
    150     order_timeout_heap = NULL;
    151   }
    152 }
    153 
    154 
    155 /**
    156  * Task run to trigger timeouts on GET /orders requests with long polling.
    157  *
    158  * @param cls unused
    159  */
    160 static void
    161 order_timeout (void *cls)
    162 {
    163   struct TMH_PendingOrder *po;
    164   struct TMH_MerchantInstance *mi;
    165 
    166   (void) cls;
    167   order_timeout_task = NULL;
    168   while (1)
    169   {
    170     po = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
    171     if (NULL == po)
    172     {
    173       /* release data structure, we don't need it right now */
    174       GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
    175       order_timeout_heap = NULL;
    176       return;
    177     }
    178     if (GNUNET_TIME_absolute_is_future (po->long_poll_timeout))
    179       break;
    180     GNUNET_assert (po ==
    181                    GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
    182     po->hn = NULL;
    183     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    184                 "Resuming long polled job due to timeout\n");
    185     mi = po->mi;
    186     GNUNET_assert (po->in_dll);
    187     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    188                                  mi->po_tail,
    189                                  po);
    190     if ( (NULL == mi->po_head) &&
    191          (NULL != mi->po_eh) )
    192     {
    193       TMH_db->event_listen_cancel (mi->po_eh);
    194       mi->po_eh = NULL;
    195     }
    196     po->in_dll = false;
    197     MHD_resume_connection (po->con);
    198     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    199   }
    200   order_timeout_task = GNUNET_SCHEDULER_add_at (po->long_poll_timeout,
    201                                                 &order_timeout,
    202                                                 NULL);
    203 }
    204 
    205 
    206 /**
    207  * Cleanup our "context", where we stored the JSON array
    208  * we are building for the response.
    209  *
    210  * @param ctx context to clean up, must be a `struct AddOrderState *`
    211  */
    212 static void
    213 cleanup (void *ctx)
    214 {
    215   struct TMH_PendingOrder *po = ctx;
    216 
    217   if (po->in_dll)
    218   {
    219     struct TMH_MerchantInstance *mi = po->mi;
    220 
    221     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    222                                  mi->po_tail,
    223                                  po);
    224   }
    225   if (NULL != po->hn)
    226     GNUNET_assert (po ==
    227                    GNUNET_CONTAINER_heap_remove_node (po->hn));
    228   json_decref (po->pa);
    229   GNUNET_free (po->summary_filter);
    230   GNUNET_free (po);
    231 }
    232 
    233 
    234 /**
    235  * Closure for #process_refunds_cb().
    236  */
    237 struct ProcessRefundsClosure
    238 {
    239   /**
    240    * Place where we accumulate the granted refunds.
    241    */
    242   struct TALER_Amount total_refund_amount;
    243 
    244   /**
    245    * Place where we accumulate the pending refunds.
    246    */
    247   struct TALER_Amount pending_refund_amount;
    248 
    249   /**
    250    * Set to an error code if something goes wrong.
    251    */
    252   enum TALER_ErrorCode ec;
    253 };
    254 
    255 
    256 /**
    257  * Function called with information about a refund.
    258  * It is responsible for summing up the refund amount.
    259  *
    260  * @param cls closure
    261  * @param refund_serial unique serial number of the refund
    262  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
    263  * @param coin_pub public coin from which the refund comes from
    264  * @param exchange_url URL of the exchange that issued @a coin_pub
    265  * @param rtransaction_id identificator of the refund
    266  * @param reason human-readable explanation of the refund
    267  * @param refund_amount refund amount which is being taken from @a coin_pub
    268  * @param pending true if the this refund was not yet processed by the wallet/exchange
    269  */
    270 static void
    271 process_refunds_cb (void *cls,
    272                     uint64_t refund_serial,
    273                     struct GNUNET_TIME_Timestamp timestamp,
    274                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
    275                     const char *exchange_url,
    276                     uint64_t rtransaction_id,
    277                     const char *reason,
    278                     const struct TALER_Amount *refund_amount,
    279                     bool pending)
    280 {
    281   struct ProcessRefundsClosure *prc = cls;
    282 
    283   if (GNUNET_OK !=
    284       TALER_amount_cmp_currency (&prc->total_refund_amount,
    285                                  refund_amount))
    286   {
    287     /* Database error, refunds in mixed currency in DB. Not OK! */
    288     prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
    289     GNUNET_break (0);
    290     return;
    291   }
    292   GNUNET_assert (0 <=
    293                  TALER_amount_add (&prc->total_refund_amount,
    294                                    &prc->total_refund_amount,
    295                                    refund_amount));
    296   if (pending)
    297     GNUNET_assert (0 <=
    298                    TALER_amount_add (&prc->pending_refund_amount,
    299                                      &prc->pending_refund_amount,
    300                                      refund_amount));
    301 }
    302 
    303 
    304 /**
    305  * Add order details to our JSON array.
    306  *
    307  * @param cls some closure
    308  * @param orig_order_id the order this is about
    309  * @param order_serial serial ID of the order
    310  * @param creation_time when was the order created
    311  */
    312 static void
    313 add_order (void *cls,
    314            const char *orig_order_id,
    315            uint64_t order_serial,
    316            struct GNUNET_TIME_Timestamp creation_time)
    317 {
    318   struct TMH_PendingOrder *po = cls;
    319   json_t *contract_terms = NULL;
    320   struct TALER_PrivateContractHashP h_contract_terms;
    321   enum GNUNET_DB_QueryStatus qs;
    322   char *order_id = NULL;
    323   bool refundable = false;
    324   bool paid;
    325   bool wired;
    326   struct TALER_MERCHANT_Contract *contract = NULL;
    327   int16_t choice_index = -1;
    328   struct ProcessRefundsClosure prc = {
    329     .ec = TALER_EC_NONE
    330   };
    331 
    332   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    333               "Adding order `%s' (%llu) to result set at instance `%s'\n",
    334               orig_order_id,
    335               (unsigned long long) order_serial,
    336               po->instance_id);
    337   qs = TMH_db->lookup_order_status_by_serial (TMH_db->cls,
    338                                               po->instance_id,
    339                                               order_serial,
    340                                               &order_id,
    341                                               &h_contract_terms,
    342                                               &paid);
    343   if (qs < 0)
    344   {
    345     GNUNET_break (0);
    346     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    347     return;
    348   }
    349   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    350   {
    351     /* Contract terms don't exist, so the order cannot be paid. */
    352     paid = false;
    353     if (NULL == orig_order_id)
    354     {
    355       /* Got a DB trigger about a new proposal, but it
    356          was already deleted again. Just ignore the event. */
    357       return;
    358     }
    359     order_id = GNUNET_strdup (orig_order_id);
    360   }
    361 
    362   {
    363     /* First try to find the order in the contracts */
    364     uint64_t os;
    365     bool session_matches;
    366 
    367     qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
    368                                          po->instance_id,
    369                                          order_id,
    370                                          NULL,
    371                                          &contract_terms,
    372                                          &os,
    373                                          &paid,
    374                                          &wired,
    375                                          &session_matches,
    376                                          NULL,
    377                                          &choice_index);
    378     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    379       GNUNET_break (os == order_serial);
    380   }
    381   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    382   {
    383     /* Might still be unclaimed, so try order table */
    384     struct TALER_MerchantPostDataHashP unused;
    385 
    386     paid = false;
    387     wired = false;
    388     qs = TMH_db->lookup_order (TMH_db->cls,
    389                                po->instance_id,
    390                                order_id,
    391                                NULL,
    392                                &unused,
    393                                &contract_terms);
    394   }
    395   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    396   {
    397     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    398                 "Order %llu disappeared during iteration. Skipping.\n",
    399                 (unsigned long long) order_serial);
    400     goto cleanup;
    401   }
    402   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    403   {
    404     GNUNET_break (0);
    405     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    406     goto cleanup;
    407   }
    408 
    409   contract = TALER_MERCHANT_contract_parse (contract_terms,
    410                                             true);
    411   if (NULL == contract)
    412   {
    413     GNUNET_break (0);
    414     po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
    415     goto cleanup;
    416   }
    417 
    418   if (paid)
    419   {
    420     const struct TALER_Amount *brutto;
    421 
    422     switch (contract->version)
    423     {
    424     case TALER_MERCHANT_CONTRACT_VERSION_0:
    425       brutto = &contract->details.v0.brutto;
    426       break;
    427     case TALER_MERCHANT_CONTRACT_VERSION_1:
    428       {
    429         struct TALER_MERCHANT_ContractChoice *choice
    430           = &contract->details.v1.choices[choice_index];
    431 
    432         GNUNET_assert (choice_index < contract->details.v1.choices_len);
    433         brutto = &choice->amount;
    434       }
    435       break;
    436     default:
    437       GNUNET_break (0);
    438       goto cleanup;
    439     }
    440     GNUNET_assert (GNUNET_OK ==
    441                    TALER_amount_set_zero (brutto->currency,
    442                                           &prc.total_refund_amount));
    443     GNUNET_assert (GNUNET_OK ==
    444                    TALER_amount_set_zero (brutto->currency,
    445                                           &prc.pending_refund_amount));
    446 
    447     qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
    448                                           po->instance_id,
    449                                           &h_contract_terms,
    450                                           &process_refunds_cb,
    451                                           &prc);
    452     if (0 > qs)
    453     {
    454       GNUNET_break (0);
    455       po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    456       goto cleanup;
    457     }
    458     if (TALER_EC_NONE != prc.ec)
    459     {
    460       GNUNET_break (0);
    461       po->result = prc.ec;
    462       goto cleanup;
    463     }
    464     if (0 > TALER_amount_cmp (&prc.total_refund_amount,
    465                               brutto))
    466       refundable = true;
    467   }
    468 
    469   switch (contract->version)
    470   {
    471   case TALER_MERCHANT_CONTRACT_VERSION_0:
    472     if (TALER_amount_is_zero (&contract->details.v0.brutto) &&
    473         (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
    474     {
    475       /* If we are actually filtering by wire status,
    476          and the order was over an amount of zero,
    477          do not return it as wire status is not
    478          exactly meaningful for orders over zero. */
    479       goto cleanup;
    480     }
    481     GNUNET_assert (
    482       0 ==
    483       json_array_append_new (
    484         po->pa,
    485         GNUNET_JSON_PACK (
    486           GNUNET_JSON_pack_string ("order_id",
    487                                    contract->order_id),
    488           GNUNET_JSON_pack_uint64 ("row_id",
    489                                    order_serial),
    490           GNUNET_JSON_pack_timestamp ("timestamp",
    491                                       creation_time),
    492           TALER_JSON_pack_amount (
    493             "amount",
    494             &contract->details.v0.brutto),
    495           GNUNET_JSON_pack_allow_null (
    496             TALER_JSON_pack_amount (
    497               "refund_amount",
    498               paid
    499             ? &prc.total_refund_amount
    500             : NULL)),
    501           GNUNET_JSON_pack_allow_null (
    502             TALER_JSON_pack_amount (
    503               "pending_refund_amount",
    504               paid
    505             ? &prc.pending_refund_amount
    506             : NULL)),
    507           TALER_JSON_pack_amount (
    508             "amount",
    509             &contract->details.v0.brutto),
    510           GNUNET_JSON_pack_string ("summary",
    511                                    contract->summary),
    512           GNUNET_JSON_pack_bool ("refundable",
    513                                  refundable),
    514           GNUNET_JSON_pack_bool ("paid",
    515                                  paid))));
    516     break;
    517   case TALER_MERCHANT_CONTRACT_VERSION_1:
    518     if (-1 == choice_index)
    519       choice_index = 0; /* default choice */
    520     GNUNET_assert (choice_index < contract->details.v1.choices_len);
    521     {
    522       struct TALER_MERCHANT_ContractChoice *choice
    523         = &contract->details.v1.choices[choice_index];
    524 
    525       GNUNET_assert (
    526         0 ==
    527         json_array_append_new (
    528           po->pa,
    529           GNUNET_JSON_PACK (
    530             GNUNET_JSON_pack_string ("order_id",
    531                                      contract->order_id),
    532             GNUNET_JSON_pack_uint64 ("row_id",
    533                                      order_serial),
    534             GNUNET_JSON_pack_timestamp ("timestamp",
    535                                         creation_time),
    536             TALER_JSON_pack_amount ("amount",
    537                                     &choice->amount),
    538             GNUNET_JSON_pack_allow_null (
    539               TALER_JSON_pack_amount (
    540                 "refund_amount",
    541                 paid
    542               ? &prc.total_refund_amount
    543               : NULL)),
    544             GNUNET_JSON_pack_allow_null (
    545               TALER_JSON_pack_amount (
    546                 "pending_refund_amount",
    547                 paid
    548               ? &prc.pending_refund_amount
    549               : NULL)),
    550             GNUNET_JSON_pack_string ("summary",
    551                                      contract->summary),
    552             GNUNET_JSON_pack_bool ("refundable",
    553                                    refundable),
    554             GNUNET_JSON_pack_bool ("paid",
    555                                    paid))));
    556     }
    557     break;
    558   default:
    559     GNUNET_break (0);
    560     goto cleanup;
    561   }
    562 
    563 cleanup:
    564   json_decref (contract_terms);
    565   GNUNET_free (order_id);
    566   if (NULL != contract)
    567   {
    568     TALER_MERCHANT_contract_free (contract);
    569     contract = NULL;
    570   }
    571 }
    572 
    573 
    574 /**
    575  * We have received a trigger from the database
    576  * that we should (possibly) resume some requests.
    577  *
    578  * @param cls a `struct TMH_MerchantInstance`
    579  * @param extra a `struct TMH_OrderChangeEventP`
    580  * @param extra_size number of bytes in @a extra
    581  */
    582 static void
    583 resume_by_event (void *cls,
    584                  const void *extra,
    585                  size_t extra_size)
    586 {
    587   struct TMH_MerchantInstance *mi = cls;
    588   const struct TMH_OrderChangeEventDetailsP *oce = extra;
    589   struct TMH_PendingOrder *pn;
    590   enum TMH_OrderStateFlags osf;
    591   uint64_t order_serial_id;
    592   struct GNUNET_TIME_Timestamp date;
    593 
    594   if (sizeof (*oce) != extra_size)
    595   {
    596     GNUNET_break (0);
    597     return;
    598   }
    599   osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
    600   order_serial_id = GNUNET_ntohll (oce->order_serial_id);
    601   date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
    602   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    603               "Received notification about new order %llu\n",
    604               (unsigned long long) order_serial_id);
    605   for (struct TMH_PendingOrder *po = mi->po_head;
    606        NULL != po;
    607        po = pn)
    608   {
    609     pn = po->next;
    610     if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
    611                (0 != (osf & TMH_OSF_PAID))) ||
    612               (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
    613             ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
    614                (0 != (osf & TMH_OSF_REFUNDED))) ||
    615               (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
    616             ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
    617                (0 != (osf & TMH_OSF_WIRED))) ||
    618               (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
    619     {
    620       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    621                   "Client %p waits on different order type\n",
    622                   po);
    623       continue;
    624     }
    625     if (po->of.delta > 0)
    626     {
    627       if (order_serial_id < po->of.start_row)
    628       {
    629         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    630                     "Client %p waits on different order row\n",
    631                     po);
    632         continue;
    633       }
    634       if (GNUNET_TIME_timestamp_cmp (date,
    635                                      <,
    636                                      po->of.date))
    637       {
    638         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    639                     "Client %p waits on different order date\n",
    640                     po);
    641         continue;
    642       }
    643       po->of.delta--;
    644     }
    645     else
    646     {
    647       if (order_serial_id > po->of.start_row)
    648       {
    649         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    650                     "Client %p waits on different order row\n",
    651                     po);
    652         continue;
    653       }
    654       if (GNUNET_TIME_timestamp_cmp (date,
    655                                      >,
    656                                      po->of.date))
    657       {
    658         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    659                     "Client %p waits on different order date\n",
    660                     po);
    661         continue;
    662       }
    663       po->of.delta++;
    664     }
    665     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    666                 "Waking up client %p!\n",
    667                 po);
    668     add_order (po,
    669                NULL,
    670                order_serial_id,
    671                date);
    672     GNUNET_assert (po->in_dll);
    673     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    674                                  mi->po_tail,
    675                                  po);
    676     po->in_dll = false;
    677     GNUNET_assert (po ==
    678                    GNUNET_CONTAINER_heap_remove_node (po->hn));
    679     po->hn = NULL;
    680     MHD_resume_connection (po->con);
    681     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    682   }
    683   if (NULL == mi->po_head)
    684   {
    685     TMH_db->event_listen_cancel (mi->po_eh);
    686     mi->po_eh = NULL;
    687   }
    688 }
    689 
    690 
    691 /**
    692  * There has been a change or addition of a new @a order_id.  Wake up
    693  * long-polling clients that may have been waiting for this event.
    694  *
    695  * @param mi the instance where the order changed
    696  * @param osf order state flags
    697  * @param date execution date of the order
    698  * @param order_serial_id serial ID of the order in the database
    699  */
    700 void
    701 TMH_notify_order_change (struct TMH_MerchantInstance *mi,
    702                          enum TMH_OrderStateFlags osf,
    703                          struct GNUNET_TIME_Timestamp date,
    704                          uint64_t order_serial_id)
    705 {
    706   struct TMH_OrderChangeEventDetailsP oce = {
    707     .order_serial_id = GNUNET_htonll (order_serial_id),
    708     .execution_date = GNUNET_TIME_timestamp_hton (date),
    709     .order_state = htonl (osf)
    710   };
    711   struct TMH_OrderChangeEventP eh = {
    712     .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
    713     .header.size = htons (sizeof (eh)),
    714     .merchant_pub = mi->merchant_pub
    715   };
    716 
    717   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    718               "Notifying clients of new order %llu at %s\n",
    719               (unsigned long long) order_serial_id,
    720               TALER_B2S (&mi->merchant_pub));
    721   TMH_db->event_notify (TMH_db->cls,
    722                         &eh.header,
    723                         &oce,
    724                         sizeof (oce));
    725 }
    726 
    727 
    728 /**
    729  * Transforms an (untrusted) input filter into a Postgresql LIKE filter.
    730  * Escapes "%" and "_" in the @a input and adds "%" at the beginning
    731  * and the end to turn the @a input into a suitable Postgresql argument.
    732  *
    733  * @param input text to turn into a substring match expression, or NULL
    734  * @return NULL if @a input was NULL, otherwise transformed @a input
    735  */
    736 static char *
    737 tr (const char *input)
    738 {
    739   char *out;
    740   size_t slen;
    741   size_t wpos;
    742 
    743   if (NULL == input)
    744     return NULL;
    745   slen = strlen (input);
    746   out = GNUNET_malloc (slen * 2 + 3);
    747   wpos = 0;
    748   out[wpos++] = '%';
    749   for (size_t i = 0; i<slen; i++)
    750   {
    751     char c = input[i];
    752 
    753     if ( (c == '%') ||
    754          (c == '_') )
    755       out[wpos++] = '\\';
    756     out[wpos++] = c;
    757   }
    758   out[wpos++] = '%';
    759   GNUNET_assert (wpos < slen * 2 + 3);
    760   return out;
    761 }
    762 
    763 
    764 /**
    765  * Handle a GET "/orders" request.
    766  *
    767  * @param rh context of the handler
    768  * @param connection the MHD connection to handle
    769  * @param[in,out] hc context with further information about the request
    770  * @return MHD result code
    771  */
    772 MHD_RESULT
    773 TMH_private_get_orders (const struct TMH_RequestHandler *rh,
    774                         struct MHD_Connection *connection,
    775                         struct TMH_HandlerContext *hc)
    776 {
    777   struct TMH_PendingOrder *po = hc->ctx;
    778   enum GNUNET_DB_QueryStatus qs;
    779 
    780   if (NULL != po)
    781   {
    782     /* resumed from long-polling, return answer we already have
    783        in 'hc->ctx' */
    784     if (TALER_EC_NONE != po->result)
    785     {
    786       GNUNET_break (0);
    787       return TALER_MHD_reply_with_error (connection,
    788                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    789                                          po->result,
    790                                          NULL);
    791     }
    792     return TALER_MHD_REPLY_JSON_PACK (
    793       connection,
    794       MHD_HTTP_OK,
    795       GNUNET_JSON_pack_array_incref ("orders",
    796                                      po->pa));
    797   }
    798   po = GNUNET_new (struct TMH_PendingOrder);
    799   hc->ctx = po;
    800   hc->cc = &cleanup;
    801   po->con = connection;
    802   po->pa = json_array ();
    803   GNUNET_assert (NULL != po->pa);
    804   po->instance_id = hc->instance->settings.id;
    805   po->mi = hc->instance;
    806 
    807   if (! (TALER_MHD_arg_to_yna (connection,
    808                                "paid",
    809                                TALER_EXCHANGE_YNA_ALL,
    810                                &po->of.paid)) )
    811   {
    812     GNUNET_break_op (0);
    813     return TALER_MHD_reply_with_error (connection,
    814                                        MHD_HTTP_BAD_REQUEST,
    815                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    816                                        "paid");
    817   }
    818   if (! (TALER_MHD_arg_to_yna (connection,
    819                                "refunded",
    820                                TALER_EXCHANGE_YNA_ALL,
    821                                &po->of.refunded)) )
    822   {
    823     GNUNET_break_op (0);
    824     return TALER_MHD_reply_with_error (connection,
    825                                        MHD_HTTP_BAD_REQUEST,
    826                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    827                                        "refunded");
    828   }
    829   if (! (TALER_MHD_arg_to_yna (connection,
    830                                "wired",
    831                                TALER_EXCHANGE_YNA_ALL,
    832                                &po->of.wired)) )
    833   {
    834     GNUNET_break_op (0);
    835     return TALER_MHD_reply_with_error (connection,
    836                                        MHD_HTTP_BAD_REQUEST,
    837                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    838                                        "wired");
    839   }
    840   po->of.delta = -20;
    841   /* deprecated in protocol v12 */
    842   TALER_MHD_parse_request_snumber (connection,
    843                                    "delta",
    844                                    &po->of.delta);
    845   /* since protocol v12 */
    846   TALER_MHD_parse_request_snumber (connection,
    847                                    "limit",
    848                                    &po->of.delta);
    849   if ( (-MAX_DELTA > po->of.delta) ||
    850        (po->of.delta > MAX_DELTA) )
    851   {
    852     GNUNET_break_op (0);
    853     return TALER_MHD_reply_with_error (connection,
    854                                        MHD_HTTP_BAD_REQUEST,
    855                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    856                                        "limit");
    857   }
    858   {
    859     const char *date_s_str;
    860 
    861     date_s_str = MHD_lookup_connection_value (connection,
    862                                               MHD_GET_ARGUMENT_KIND,
    863                                               "date_s");
    864     if (NULL == date_s_str)
    865     {
    866       if (po->of.delta > 0)
    867         po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
    868       else
    869         po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
    870     }
    871     else
    872     {
    873       char dummy;
    874       unsigned long long ll;
    875 
    876       if (1 !=
    877           sscanf (date_s_str,
    878                   "%llu%c",
    879                   &ll,
    880                   &dummy))
    881       {
    882         GNUNET_break_op (0);
    883         return TALER_MHD_reply_with_error (connection,
    884                                            MHD_HTTP_BAD_REQUEST,
    885                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
    886                                            "date_s");
    887       }
    888 
    889       po->of.date = GNUNET_TIME_absolute_to_timestamp (
    890         GNUNET_TIME_absolute_from_s (ll));
    891       if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
    892       {
    893         GNUNET_break_op (0);
    894         return TALER_MHD_reply_with_error (connection,
    895                                            MHD_HTTP_BAD_REQUEST,
    896                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
    897                                            "date_s");
    898       }
    899     }
    900   }
    901   if (po->of.delta > 0)
    902     po->of.start_row = 0;
    903   else
    904     po->of.start_row = INT64_MAX;
    905   /* deprecated in protocol v12 */
    906   TALER_MHD_parse_request_number (connection,
    907                                   "start",
    908                                   &po->of.start_row);
    909   /* since protocol v12 */
    910   TALER_MHD_parse_request_number (connection,
    911                                   "offset",
    912                                   &po->of.start_row);
    913   if (INT64_MAX < po->of.start_row)
    914   {
    915     GNUNET_break_op (0);
    916     return TALER_MHD_reply_with_error (connection,
    917                                        MHD_HTTP_BAD_REQUEST,
    918                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    919                                        "offset");
    920   }
    921   po->summary_filter = tr (MHD_lookup_connection_value (connection,
    922                                                         MHD_GET_ARGUMENT_KIND,
    923                                                         "summary_filter"));
    924   po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
    925   po->of.session_id
    926     = MHD_lookup_connection_value (connection,
    927                                    MHD_GET_ARGUMENT_KIND,
    928                                    "session_id");
    929   po->of.fulfillment_url
    930     = MHD_lookup_connection_value (connection,
    931                                    MHD_GET_ARGUMENT_KIND,
    932                                    "fulfillment_url");
    933   TALER_MHD_parse_request_timeout (connection,
    934                                    &po->long_poll_timeout);
    935   if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
    936   {
    937     GNUNET_break_op (0);
    938     return TALER_MHD_reply_with_error (connection,
    939                                        MHD_HTTP_BAD_REQUEST,
    940                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    941                                        "timeout_ms");
    942   }
    943   po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
    944   if ( (0 >= po->of.delta) &&
    945        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
    946   {
    947     GNUNET_break_op (0);
    948     po->of.timeout = GNUNET_TIME_UNIT_ZERO;
    949     po->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
    950   }
    951 
    952   qs = TMH_db->lookup_orders (TMH_db->cls,
    953                               po->instance_id,
    954                               &po->of,
    955                               &add_order,
    956                               po);
    957   if (0 > qs)
    958   {
    959     GNUNET_break (0);
    960     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    961   }
    962   if (TALER_EC_NONE != po->result)
    963   {
    964     GNUNET_break (0);
    965     return TALER_MHD_reply_with_error (connection,
    966                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    967                                        po->result,
    968                                        NULL);
    969   }
    970   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
    971        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
    972   {
    973     struct TMH_MerchantInstance *mi = hc->instance;
    974 
    975     /* setup timeout heap (if not yet exists) */
    976     if (NULL == order_timeout_heap)
    977       order_timeout_heap
    978         = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
    979     po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
    980                                            po,
    981                                            po->long_poll_timeout.abs_value_us);
    982     GNUNET_CONTAINER_DLL_insert (mi->po_head,
    983                                  mi->po_tail,
    984                                  po);
    985     po->in_dll = true;
    986     if (NULL == mi->po_eh)
    987     {
    988       struct TMH_OrderChangeEventP change_eh = {
    989         .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
    990         .header.size = htons (sizeof (change_eh)),
    991         .merchant_pub = mi->merchant_pub
    992       };
    993 
    994       mi->po_eh = TMH_db->event_listen (TMH_db->cls,
    995                                         &change_eh.header,
    996                                         GNUNET_TIME_UNIT_FOREVER_REL,
    997                                         &resume_by_event,
    998                                         mi);
    999     }
   1000     MHD_suspend_connection (connection);
   1001     {
   1002       struct TMH_PendingOrder *pot;
   1003 
   1004       /* start timeout task */
   1005       pot = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
   1006       if (NULL != order_timeout_task)
   1007         GNUNET_SCHEDULER_cancel (order_timeout_task);
   1008       order_timeout_task = GNUNET_SCHEDULER_add_at (pot->long_poll_timeout,
   1009                                                     &order_timeout,
   1010                                                     NULL);
   1011     }
   1012     return MHD_YES;
   1013   }
   1014   return TALER_MHD_REPLY_JSON_PACK (
   1015     connection,
   1016     MHD_HTTP_OK,
   1017     GNUNET_JSON_pack_array_incref ("orders",
   1018                                    po->pa));
   1019 }
   1020 
   1021 
   1022 /* end of taler-merchant-httpd_private-get-orders.c */