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