Line data Source code
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 102 : TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
123 : {
124 : struct TMH_PendingOrder *po;
125 :
126 102 : while (NULL != (po = mi->po_head))
127 : {
128 0 : GNUNET_assert (po->in_dll);
129 0 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
130 : mi->po_tail,
131 : po);
132 0 : GNUNET_assert (po ==
133 : GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
134 0 : MHD_resume_connection (po->con);
135 0 : po->in_dll = false;
136 : }
137 102 : if (NULL != mi->po_eh)
138 : {
139 0 : TMH_db->event_listen_cancel (mi->po_eh);
140 0 : mi->po_eh = NULL;
141 : }
142 102 : if (NULL != order_timeout_task)
143 : {
144 2 : GNUNET_SCHEDULER_cancel (order_timeout_task);
145 2 : order_timeout_task = NULL;
146 : }
147 102 : if (NULL != order_timeout_heap)
148 : {
149 2 : GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
150 2 : order_timeout_heap = NULL;
151 : }
152 102 : }
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 0 : order_timeout (void *cls)
162 : {
163 : struct TMH_PendingOrder *po;
164 : struct TMH_MerchantInstance *mi;
165 :
166 : (void) cls;
167 0 : order_timeout_task = NULL;
168 : while (1)
169 : {
170 0 : po = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
171 0 : if (NULL == po)
172 : {
173 : /* release data structure, we don't need it right now */
174 0 : GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
175 0 : order_timeout_heap = NULL;
176 0 : return;
177 : }
178 0 : if (GNUNET_TIME_absolute_is_future (po->long_poll_timeout))
179 0 : break;
180 0 : GNUNET_assert (po ==
181 : GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
182 0 : po->hn = NULL;
183 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
184 : "Resuming long polled job due to timeout\n");
185 0 : mi = po->mi;
186 0 : GNUNET_assert (po->in_dll);
187 0 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
188 : mi->po_tail,
189 : po);
190 0 : if ( (NULL == mi->po_head) &&
191 0 : (NULL != mi->po_eh) )
192 : {
193 0 : TMH_db->event_listen_cancel (mi->po_eh);
194 0 : mi->po_eh = NULL;
195 : }
196 0 : po->in_dll = false;
197 0 : MHD_resume_connection (po->con);
198 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
199 : }
200 0 : 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 14 : cleanup (void *ctx)
214 : {
215 14 : struct TMH_PendingOrder *po = ctx;
216 :
217 14 : if (po->in_dll)
218 : {
219 0 : struct TMH_MerchantInstance *mi = po->mi;
220 :
221 0 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
222 : mi->po_tail,
223 : po);
224 : }
225 14 : if (NULL != po->hn)
226 0 : GNUNET_assert (po ==
227 : GNUNET_CONTAINER_heap_remove_node (po->hn));
228 14 : json_decref (po->pa);
229 14 : GNUNET_free (po->summary_filter);
230 14 : GNUNET_free (po);
231 14 : }
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 0 : 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 0 : struct ProcessRefundsClosure *prc = cls;
282 :
283 0 : if (GNUNET_OK !=
284 0 : TALER_amount_cmp_currency (&prc->total_refund_amount,
285 : refund_amount))
286 : {
287 : /* Database error, refunds in mixed currency in DB. Not OK! */
288 0 : prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
289 0 : GNUNET_break (0);
290 0 : return;
291 : }
292 0 : GNUNET_assert (0 <=
293 : TALER_amount_add (&prc->total_refund_amount,
294 : &prc->total_refund_amount,
295 : refund_amount));
296 0 : if (pending)
297 0 : 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 17 : add_order (void *cls,
314 : const char *orig_order_id,
315 : uint64_t order_serial,
316 : struct GNUNET_TIME_Timestamp creation_time)
317 : {
318 17 : struct TMH_PendingOrder *po = cls;
319 17 : json_t *contract_terms = NULL;
320 : struct TALER_PrivateContractHashP h_contract_terms;
321 : enum GNUNET_DB_QueryStatus qs;
322 17 : char *order_id = NULL;
323 17 : bool refundable = false;
324 : bool paid;
325 : bool wired;
326 17 : struct TALER_MERCHANT_Contract *contract = NULL;
327 17 : int16_t choice_index = -1;
328 17 : struct ProcessRefundsClosure prc = {
329 : .ec = TALER_EC_NONE
330 : };
331 :
332 17 : 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 17 : 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 17 : if (qs < 0)
344 : {
345 0 : GNUNET_break (0);
346 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
347 2 : return;
348 : }
349 17 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
350 : {
351 : /* Contract terms don't exist, so the order cannot be paid. */
352 3 : paid = false;
353 3 : 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 2 : return;
358 : }
359 1 : 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 15 : 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 15 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
379 14 : GNUNET_break (os == order_serial);
380 : }
381 15 : 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 1 : paid = false;
387 1 : wired = false;
388 1 : qs = TMH_db->lookup_order (TMH_db->cls,
389 : po->instance_id,
390 : order_id,
391 : NULL,
392 : &unused,
393 : &contract_terms);
394 : }
395 15 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
396 : {
397 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
398 : "Order %llu disappeared during iteration. Skipping.\n",
399 : (unsigned long long) order_serial);
400 0 : goto cleanup;
401 : }
402 15 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
403 : {
404 0 : GNUNET_break (0);
405 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
406 0 : goto cleanup;
407 : }
408 :
409 15 : contract = TALER_MERCHANT_contract_parse (contract_terms,
410 : true);
411 15 : if (NULL == contract)
412 : {
413 0 : GNUNET_break (0);
414 0 : po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
415 0 : goto cleanup;
416 : }
417 :
418 15 : if (paid)
419 : {
420 : const struct TALER_Amount *brutto;
421 :
422 5 : switch (contract->version)
423 : {
424 5 : case TALER_MERCHANT_CONTRACT_VERSION_0:
425 5 : brutto = &contract->details.v0.brutto;
426 5 : break;
427 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
428 : {
429 0 : struct TALER_MERCHANT_ContractChoice *choice
430 0 : = &contract->details.v1.choices[choice_index];
431 :
432 0 : GNUNET_assert (choice_index < contract->details.v1.choices_len);
433 0 : brutto = &choice->amount;
434 : }
435 0 : break;
436 0 : default:
437 0 : GNUNET_break (0);
438 0 : goto cleanup;
439 : }
440 5 : GNUNET_assert (GNUNET_OK ==
441 : TALER_amount_set_zero (brutto->currency,
442 : &prc.total_refund_amount));
443 5 : GNUNET_assert (GNUNET_OK ==
444 : TALER_amount_set_zero (brutto->currency,
445 : &prc.pending_refund_amount));
446 :
447 5 : qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
448 : po->instance_id,
449 : &h_contract_terms,
450 : &process_refunds_cb,
451 : &prc);
452 5 : if (0 > qs)
453 : {
454 0 : GNUNET_break (0);
455 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
456 0 : goto cleanup;
457 : }
458 5 : if (TALER_EC_NONE != prc.ec)
459 : {
460 0 : GNUNET_break (0);
461 0 : po->result = prc.ec;
462 0 : goto cleanup;
463 : }
464 5 : if (0 > TALER_amount_cmp (&prc.total_refund_amount,
465 : brutto))
466 5 : refundable = true;
467 : }
468 :
469 15 : switch (contract->version)
470 : {
471 15 : case TALER_MERCHANT_CONTRACT_VERSION_0:
472 15 : if (TALER_amount_is_zero (&contract->details.v0.brutto) &&
473 0 : (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 0 : goto cleanup;
480 : }
481 15 : 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 15 : break;
517 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
518 0 : if (-1 == choice_index)
519 0 : choice_index = 0; /* default choice */
520 0 : GNUNET_assert (choice_index < contract->details.v1.choices_len);
521 : {
522 0 : struct TALER_MERCHANT_ContractChoice *choice
523 0 : = &contract->details.v1.choices[choice_index];
524 :
525 0 : 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 0 : break;
558 0 : default:
559 0 : GNUNET_break (0);
560 0 : goto cleanup;
561 : }
562 :
563 15 : cleanup:
564 15 : json_decref (contract_terms);
565 15 : GNUNET_free (order_id);
566 15 : if (NULL != contract)
567 : {
568 15 : TALER_MERCHANT_contract_free (contract);
569 15 : 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 2 : resume_by_event (void *cls,
584 : const void *extra,
585 : size_t extra_size)
586 : {
587 2 : struct TMH_MerchantInstance *mi = cls;
588 2 : 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 2 : if (sizeof (*oce) != extra_size)
595 : {
596 0 : GNUNET_break (0);
597 0 : return;
598 : }
599 2 : osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
600 2 : order_serial_id = GNUNET_ntohll (oce->order_serial_id);
601 2 : date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
602 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
603 : "Received notification about new order %llu\n",
604 : (unsigned long long) order_serial_id);
605 2 : for (struct TMH_PendingOrder *po = mi->po_head;
606 4 : NULL != po;
607 2 : po = pn)
608 : {
609 2 : pn = po->next;
610 4 : if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
611 2 : (0 != (osf & TMH_OSF_PAID))) ||
612 0 : (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
613 2 : ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
614 2 : (0 != (osf & TMH_OSF_REFUNDED))) ||
615 0 : (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
616 2 : ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
617 2 : (0 != (osf & TMH_OSF_WIRED))) ||
618 0 : (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
619 : {
620 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
621 : "Client %p waits on different order type\n",
622 : po);
623 0 : continue;
624 : }
625 2 : if (po->of.delta > 0)
626 : {
627 2 : if (order_serial_id < po->of.start_row)
628 : {
629 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
630 : "Client %p waits on different order row\n",
631 : po);
632 0 : continue;
633 : }
634 2 : if (GNUNET_TIME_timestamp_cmp (date,
635 : <,
636 : po->of.date))
637 : {
638 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
639 : "Client %p waits on different order date\n",
640 : po);
641 0 : continue;
642 : }
643 2 : po->of.delta--;
644 : }
645 : else
646 : {
647 0 : if (order_serial_id > po->of.start_row)
648 : {
649 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
650 : "Client %p waits on different order row\n",
651 : po);
652 0 : continue;
653 : }
654 0 : if (GNUNET_TIME_timestamp_cmp (date,
655 : >,
656 : po->of.date))
657 : {
658 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
659 : "Client %p waits on different order date\n",
660 : po);
661 0 : continue;
662 : }
663 0 : po->of.delta++;
664 : }
665 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
666 : "Waking up client %p!\n",
667 : po);
668 2 : add_order (po,
669 : NULL,
670 : order_serial_id,
671 : date);
672 2 : GNUNET_assert (po->in_dll);
673 2 : GNUNET_CONTAINER_DLL_remove (mi->po_head,
674 : mi->po_tail,
675 : po);
676 2 : po->in_dll = false;
677 2 : GNUNET_assert (po ==
678 : GNUNET_CONTAINER_heap_remove_node (po->hn));
679 2 : po->hn = NULL;
680 2 : MHD_resume_connection (po->con);
681 2 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
682 : }
683 2 : if (NULL == mi->po_head)
684 : {
685 2 : TMH_db->event_listen_cancel (mi->po_eh);
686 2 : 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 154 : 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 308 : struct TMH_OrderChangeEventDetailsP oce = {
707 154 : .order_serial_id = GNUNET_htonll (order_serial_id),
708 154 : .execution_date = GNUNET_TIME_timestamp_hton (date),
709 154 : .order_state = htonl (osf)
710 : };
711 154 : struct TMH_OrderChangeEventP eh = {
712 154 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
713 154 : .header.size = htons (sizeof (eh)),
714 : .merchant_pub = mi->merchant_pub
715 : };
716 :
717 154 : 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 154 : TMH_db->event_notify (TMH_db->cls,
722 : &eh.header,
723 : &oce,
724 : sizeof (oce));
725 154 : }
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 14 : tr (const char *input)
738 : {
739 : char *out;
740 : size_t slen;
741 : size_t wpos;
742 :
743 14 : if (NULL == input)
744 14 : return NULL;
745 0 : slen = strlen (input);
746 0 : out = GNUNET_malloc (slen * 2 + 3);
747 0 : wpos = 0;
748 0 : out[wpos++] = '%';
749 0 : for (size_t i = 0; i<slen; i++)
750 : {
751 0 : char c = input[i];
752 :
753 0 : if ( (c == '%') ||
754 : (c == '_') )
755 0 : out[wpos++] = '\\';
756 0 : out[wpos++] = c;
757 : }
758 0 : out[wpos++] = '%';
759 0 : GNUNET_assert (wpos < slen * 2 + 3);
760 0 : 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 16 : TMH_private_get_orders (const struct TMH_RequestHandler *rh,
774 : struct MHD_Connection *connection,
775 : struct TMH_HandlerContext *hc)
776 : {
777 16 : struct TMH_PendingOrder *po = hc->ctx;
778 : enum GNUNET_DB_QueryStatus qs;
779 :
780 16 : if (NULL != po)
781 : {
782 : /* resumed from long-polling, return answer we already have
783 : in 'hc->ctx' */
784 2 : if (TALER_EC_NONE != po->result)
785 : {
786 0 : GNUNET_break (0);
787 0 : return TALER_MHD_reply_with_error (connection,
788 : MHD_HTTP_INTERNAL_SERVER_ERROR,
789 : po->result,
790 : NULL);
791 : }
792 2 : return TALER_MHD_REPLY_JSON_PACK (
793 : connection,
794 : MHD_HTTP_OK,
795 : GNUNET_JSON_pack_array_incref ("orders",
796 : po->pa));
797 : }
798 14 : po = GNUNET_new (struct TMH_PendingOrder);
799 14 : hc->ctx = po;
800 14 : hc->cc = &cleanup;
801 14 : po->con = connection;
802 14 : po->pa = json_array ();
803 14 : GNUNET_assert (NULL != po->pa);
804 14 : po->instance_id = hc->instance->settings.id;
805 14 : po->mi = hc->instance;
806 :
807 14 : if (! (TALER_MHD_arg_to_yna (connection,
808 : "paid",
809 : TALER_EXCHANGE_YNA_ALL,
810 : &po->of.paid)) )
811 : {
812 0 : GNUNET_break_op (0);
813 0 : return TALER_MHD_reply_with_error (connection,
814 : MHD_HTTP_BAD_REQUEST,
815 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
816 : "paid");
817 : }
818 14 : if (! (TALER_MHD_arg_to_yna (connection,
819 : "refunded",
820 : TALER_EXCHANGE_YNA_ALL,
821 : &po->of.refunded)) )
822 : {
823 0 : GNUNET_break_op (0);
824 0 : return TALER_MHD_reply_with_error (connection,
825 : MHD_HTTP_BAD_REQUEST,
826 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
827 : "refunded");
828 : }
829 14 : if (! (TALER_MHD_arg_to_yna (connection,
830 : "wired",
831 : TALER_EXCHANGE_YNA_ALL,
832 : &po->of.wired)) )
833 : {
834 0 : GNUNET_break_op (0);
835 0 : return TALER_MHD_reply_with_error (connection,
836 : MHD_HTTP_BAD_REQUEST,
837 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
838 : "wired");
839 : }
840 14 : po->of.delta = -20;
841 : /* deprecated in protocol v12 */
842 14 : TALER_MHD_parse_request_snumber (connection,
843 : "delta",
844 : &po->of.delta);
845 : /* since protocol v12 */
846 14 : TALER_MHD_parse_request_snumber (connection,
847 : "limit",
848 : &po->of.delta);
849 14 : if ( (-MAX_DELTA > po->of.delta) ||
850 14 : (po->of.delta > MAX_DELTA) )
851 : {
852 0 : GNUNET_break_op (0);
853 0 : 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 14 : date_s_str = MHD_lookup_connection_value (connection,
862 : MHD_GET_ARGUMENT_KIND,
863 : "date_s");
864 14 : if (NULL == date_s_str)
865 : {
866 14 : if (po->of.delta > 0)
867 2 : po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
868 : else
869 12 : po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
870 : }
871 : else
872 : {
873 : char dummy;
874 : unsigned long long ll;
875 :
876 0 : if (1 !=
877 0 : sscanf (date_s_str,
878 : "%llu%c",
879 : &ll,
880 : &dummy))
881 : {
882 0 : GNUNET_break_op (0);
883 0 : return TALER_MHD_reply_with_error (connection,
884 : MHD_HTTP_BAD_REQUEST,
885 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
886 : "date_s");
887 : }
888 :
889 0 : po->of.date = GNUNET_TIME_absolute_to_timestamp (
890 : GNUNET_TIME_absolute_from_s (ll));
891 0 : if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
892 : {
893 0 : GNUNET_break_op (0);
894 0 : 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 14 : if (po->of.delta > 0)
902 2 : po->of.start_row = 0;
903 : else
904 12 : po->of.start_row = INT64_MAX;
905 : /* deprecated in protocol v12 */
906 14 : TALER_MHD_parse_request_number (connection,
907 : "start",
908 : &po->of.start_row);
909 : /* since protocol v12 */
910 14 : TALER_MHD_parse_request_number (connection,
911 : "offset",
912 : &po->of.start_row);
913 14 : if (INT64_MAX < po->of.start_row)
914 : {
915 0 : GNUNET_break_op (0);
916 0 : return TALER_MHD_reply_with_error (connection,
917 : MHD_HTTP_BAD_REQUEST,
918 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
919 : "offset");
920 : }
921 14 : po->summary_filter = tr (MHD_lookup_connection_value (connection,
922 : MHD_GET_ARGUMENT_KIND,
923 : "summary_filter"));
924 14 : po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
925 : po->of.session_id
926 14 : = MHD_lookup_connection_value (connection,
927 : MHD_GET_ARGUMENT_KIND,
928 : "session_id");
929 : po->of.fulfillment_url
930 14 : = MHD_lookup_connection_value (connection,
931 : MHD_GET_ARGUMENT_KIND,
932 : "fulfillment_url");
933 14 : TALER_MHD_parse_request_timeout (connection,
934 : &po->long_poll_timeout);
935 14 : if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
936 : {
937 0 : GNUNET_break_op (0);
938 0 : return TALER_MHD_reply_with_error (connection,
939 : MHD_HTTP_BAD_REQUEST,
940 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
941 : "timeout_ms");
942 : }
943 14 : po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
944 26 : if ( (0 >= po->of.delta) &&
945 12 : (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
946 : {
947 0 : GNUNET_break_op (0);
948 0 : po->of.timeout = GNUNET_TIME_UNIT_ZERO;
949 0 : po->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
950 : }
951 :
952 14 : qs = TMH_db->lookup_orders (TMH_db->cls,
953 : po->instance_id,
954 14 : &po->of,
955 : &add_order,
956 : po);
957 14 : if (0 > qs)
958 : {
959 0 : GNUNET_break (0);
960 0 : po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
961 : }
962 14 : if (TALER_EC_NONE != po->result)
963 : {
964 0 : GNUNET_break (0);
965 0 : return TALER_MHD_reply_with_error (connection,
966 : MHD_HTTP_INTERNAL_SERVER_ERROR,
967 : po->result,
968 : NULL);
969 : }
970 20 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
971 6 : (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
972 : {
973 2 : struct TMH_MerchantInstance *mi = hc->instance;
974 :
975 : /* setup timeout heap (if not yet exists) */
976 2 : if (NULL == order_timeout_heap)
977 : order_timeout_heap
978 2 : = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
979 2 : po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
980 : po,
981 : po->long_poll_timeout.abs_value_us);
982 2 : GNUNET_CONTAINER_DLL_insert (mi->po_head,
983 : mi->po_tail,
984 : po);
985 2 : po->in_dll = true;
986 2 : if (NULL == mi->po_eh)
987 : {
988 2 : struct TMH_OrderChangeEventP change_eh = {
989 2 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
990 2 : .header.size = htons (sizeof (change_eh)),
991 : .merchant_pub = mi->merchant_pub
992 : };
993 :
994 2 : mi->po_eh = TMH_db->event_listen (TMH_db->cls,
995 : &change_eh.header,
996 2 : GNUNET_TIME_UNIT_FOREVER_REL,
997 : &resume_by_event,
998 : mi);
999 : }
1000 2 : MHD_suspend_connection (connection);
1001 : {
1002 : struct TMH_PendingOrder *pot;
1003 :
1004 : /* start timeout task */
1005 2 : pot = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
1006 2 : if (NULL != order_timeout_task)
1007 0 : GNUNET_SCHEDULER_cancel (order_timeout_task);
1008 2 : order_timeout_task = GNUNET_SCHEDULER_add_at (pot->long_poll_timeout,
1009 : &order_timeout,
1010 : NULL);
1011 : }
1012 2 : return MHD_YES;
1013 : }
1014 12 : 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 */
|