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