Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2022-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 src/backend/taler-merchant-httpd_get-templates-TEMPLATE_ID.c
18 : * @brief implement GET /templates/$ID
19 : * @author Priscilla HUANG
20 : */
21 : #include "platform.h"
22 : #include "taler-merchant-httpd_get-templates-TEMPLATE_ID.h"
23 : #include "taler-merchant-httpd_exchanges.h"
24 : #include "taler-merchant-httpd_helper.h"
25 : #include <taler/taler_json_lib.h>
26 : #include "merchant-database/iterate_categories_by_ids.h"
27 : #include "merchant-database/iterate_custom_units_by_names.h"
28 : #include "merchant-database/iterate_inventory_products.h"
29 : #include "merchant-database/iterate_inventory_products_filtered.h"
30 : #include "merchant-database/get_template.h"
31 :
32 :
33 : /**
34 : * How long do we wait at most for exchange /keys data?
35 : */
36 : #define MAX_KEYS_WAIT \
37 : GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
38 :
39 :
40 : struct GetTemplateContext;
41 :
42 :
43 : /**
44 : * Pending lookup of one exchange's keys.
45 : */
46 : struct ExchangeLookup
47 : {
48 : /**
49 : * Kept in a DLL.
50 : */
51 : struct ExchangeLookup *next;
52 :
53 : /**
54 : * Kept in a DLL.
55 : */
56 : struct ExchangeLookup *prev;
57 :
58 : /**
59 : * Parent request.
60 : */
61 : struct GetTemplateContext *gtc;
62 :
63 : /**
64 : * Exchange base URL.
65 : */
66 : char *url;
67 :
68 : /**
69 : * Pending keys operation.
70 : */
71 : struct TMH_EXCHANGES_KeysOperation *ko;
72 : };
73 :
74 :
75 : /**
76 : * Context for a wallet-facing template lookup.
77 : */
78 : struct GetTemplateContext
79 : {
80 : /**
81 : * Kept in a DLL while suspended.
82 : */
83 : struct GetTemplateContext *next;
84 :
85 : /**
86 : * Kept in a DLL while suspended.
87 : */
88 : struct GetTemplateContext *prev;
89 :
90 : /**
91 : * Request handler context.
92 : */
93 : struct TMH_HandlerContext *hc;
94 :
95 : /**
96 : * Connection handling this request.
97 : */
98 : struct MHD_Connection *connection;
99 :
100 : /**
101 : * Pending exchange lookups.
102 : */
103 : struct ExchangeLookup *lookup_head;
104 :
105 : /**
106 : * Pending exchange lookups.
107 : */
108 : struct ExchangeLookup *lookup_tail;
109 :
110 : /**
111 : * Task that limits exchange lookup latency.
112 : */
113 : struct GNUNET_SCHEDULER_Task *timeout_task;
114 :
115 : /**
116 : * Wallet-facing template contract.
117 : */
118 : json_t *template_contract;
119 :
120 : /**
121 : * Editable defaults, NULL if absent.
122 : */
123 : json_t *editable_defaults;
124 :
125 : /**
126 : * Merchant information snapshot.
127 : */
128 : json_t *merchant;
129 :
130 : /**
131 : * Compatible exchange candidates found so far.
132 : */
133 : json_t *exchange_candidates;
134 :
135 : /**
136 : * Merchant public key snapshot.
137 : */
138 : struct TALER_MerchantPublicKeyP merchant_pub;
139 :
140 : /**
141 : * #GNUNET_YES while suspended, #GNUNET_NO otherwise, and
142 : * #GNUNET_SYSERR when resumed for shutdown.
143 : */
144 : enum GNUNET_GenericReturnValue suspended;
145 : };
146 :
147 :
148 : /**
149 : * Head of suspended template requests.
150 : */
151 : static struct GetTemplateContext *gtc_head;
152 :
153 : /**
154 : * Tail of suspended template requests.
155 : */
156 : static struct GetTemplateContext *gtc_tail;
157 :
158 :
159 : /**
160 : * Determine the currency the client must pay in, if the template
161 : * requires a particular one without fixing the amount.
162 : *
163 : * @param template_contract contract of the template
164 : * @return currency to require, NULL if the template does not say
165 : */
166 : static const char *
167 8 : get_required_currency (const json_t *template_contract)
168 : {
169 : const json_t *currency;
170 :
171 8 : currency = json_object_get (template_contract,
172 : "currency");
173 8 : if (NULL == currency)
174 7 : return NULL;
175 1 : if (! json_is_string (currency))
176 : {
177 0 : GNUNET_break (0);
178 0 : return NULL;
179 : }
180 1 : return json_string_value (currency);
181 : }
182 :
183 :
184 : /**
185 : * Test whether @a method is already in @a methods.
186 : *
187 : * @param methods JSON array of strings
188 : * @param method method to find
189 : * @return true if present
190 : */
191 : static bool
192 8 : wire_method_present (const json_t *methods,
193 : const char *method)
194 : {
195 : const json_t *entry;
196 : size_t index;
197 :
198 8 : json_array_foreach (methods, index, entry)
199 : {
200 0 : if (0 == strcasecmp (json_string_value (entry),
201 : method))
202 0 : return true;
203 : }
204 8 : return false;
205 : }
206 :
207 :
208 : /**
209 : * Test whether an exchange has an account using @a wire_method.
210 : *
211 : * @param keys exchange keys
212 : * @param wire_method wire method to find
213 : * @return true if the method is supported
214 : */
215 : static bool
216 8 : exchange_has_wire_method (const struct TALER_EXCHANGE_Keys *keys,
217 : const char *wire_method)
218 : {
219 8 : for (unsigned int i = 0; i<keys->accounts_len; i++)
220 : {
221 8 : struct TALER_FullPayto full_payto = keys->accounts[i].fpayto_uri;
222 : char *method;
223 : bool matches;
224 :
225 8 : method = TALER_payto_get_method (full_payto.full_payto);
226 8 : matches = (0 == strcasecmp (method,
227 : wire_method));
228 8 : GNUNET_free (method);
229 8 : if (matches)
230 8 : return true;
231 : }
232 0 : return false;
233 : }
234 :
235 :
236 : /**
237 : * Add an exchange candidate based on current key data.
238 : *
239 : * @param gtc request context
240 : * @param url exchange base URL
241 : * @param keys exchange keys
242 : * @param exchange exchange handle
243 : */
244 : static void
245 8 : add_exchange_candidate (struct GetTemplateContext *gtc,
246 : const char *url,
247 : const struct TALER_EXCHANGE_Keys *keys,
248 : const struct TMH_Exchange *exchange)
249 : {
250 : const char *currency;
251 : json_t *wire_methods;
252 :
253 8 : if (NULL == keys)
254 0 : return;
255 8 : currency = TMH_EXCHANGES_get_currency (exchange);
256 8 : if ( (NULL == currency) ||
257 8 : (0 != strcasecmp (currency,
258 8 : keys->currency)) )
259 0 : return;
260 8 : wire_methods = json_array ();
261 8 : GNUNET_assert (NULL != wire_methods);
262 8 : for (const struct TMH_WireMethod *wm = gtc->hc->instance->wm_head;
263 16 : NULL != wm;
264 8 : wm = wm->next)
265 : {
266 : struct TALER_NormalizedPayto np;
267 : bool allowed;
268 :
269 8 : if (! wm->active)
270 0 : continue;
271 8 : if (! exchange_has_wire_method (keys,
272 8 : wm->wire_method))
273 0 : continue;
274 8 : np = TALER_payto_normalize (wm->payto_uri);
275 8 : allowed = TALER_EXCHANGE_keys_test_account_allowed (keys,
276 : false,
277 : np);
278 8 : GNUNET_free (np.normalized_payto);
279 16 : if ( (! allowed) ||
280 8 : wire_method_present (wire_methods,
281 8 : wm->wire_method) )
282 0 : continue;
283 8 : GNUNET_assert (0 ==
284 : json_array_append_new (wire_methods,
285 : json_string (wm->wire_method)));
286 : }
287 8 : if (0 == json_array_size (wire_methods))
288 : {
289 0 : json_decref (wire_methods);
290 0 : return;
291 : }
292 8 : GNUNET_assert (0 ==
293 : json_array_append_new (
294 : gtc->exchange_candidates,
295 : GNUNET_JSON_PACK (
296 : GNUNET_JSON_pack_string ("base_url",
297 : url),
298 : GNUNET_JSON_pack_string ("currency",
299 : currency),
300 : GNUNET_JSON_pack_data_auto (
301 : "master_pub",
302 : TMH_EXCHANGES_get_master_pub (exchange)),
303 : GNUNET_JSON_pack_array_steal ("wire_methods",
304 : wire_methods))));
305 : }
306 :
307 :
308 : /**
309 : * Resume a template request after exchange discovery.
310 : *
311 : * @param gtc request to resume
312 : */
313 : static void
314 8 : resume_template_request (struct GetTemplateContext *gtc)
315 : {
316 8 : if (GNUNET_YES != gtc->suspended)
317 0 : return;
318 8 : if (NULL != gtc->timeout_task)
319 : {
320 8 : GNUNET_SCHEDULER_cancel (gtc->timeout_task);
321 8 : gtc->timeout_task = NULL;
322 : }
323 8 : GNUNET_CONTAINER_DLL_remove (gtc_head,
324 : gtc_tail,
325 : gtc);
326 8 : gtc->suspended = GNUNET_NO;
327 8 : MHD_resume_connection (gtc->connection);
328 8 : TALER_MHD_daemon_trigger ();
329 : }
330 :
331 :
332 : /**
333 : * Exchange keys lookup completed.
334 : *
335 : * @param cls a `struct ExchangeLookup *`
336 : * @param keys exchange keys, NULL on failure
337 : * @param exchange exchange handle
338 : */
339 : static void
340 8 : exchange_keys_cb (void *cls,
341 : struct TALER_EXCHANGE_Keys *keys,
342 : struct TMH_Exchange *exchange)
343 : {
344 8 : struct ExchangeLookup *lookup = cls;
345 8 : struct GetTemplateContext *gtc = lookup->gtc;
346 :
347 8 : lookup->ko = NULL;
348 8 : GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
349 : gtc->lookup_tail,
350 : lookup);
351 8 : add_exchange_candidate (gtc,
352 8 : lookup->url,
353 : keys,
354 : exchange);
355 8 : GNUNET_free (lookup->url);
356 8 : GNUNET_free (lookup);
357 8 : if (NULL == gtc->lookup_head)
358 8 : resume_template_request (gtc);
359 8 : }
360 :
361 :
362 : /**
363 : * Start key discovery for one trusted exchange.
364 : *
365 : * @param cls a `struct GetTemplateContext *`
366 : * @param url exchange base URL
367 : * @param exchange exchange handle, unused
368 : */
369 : static void
370 8 : start_exchange_lookup (void *cls,
371 : const char *url,
372 : const struct TMH_Exchange *exchange)
373 : {
374 8 : struct GetTemplateContext *gtc = cls;
375 : struct ExchangeLookup *lookup;
376 :
377 : (void) exchange;
378 8 : lookup = GNUNET_new (struct ExchangeLookup);
379 8 : lookup->gtc = gtc;
380 8 : lookup->url = GNUNET_strdup (url);
381 8 : lookup->ko = TMH_EXCHANGES_keys4exchange (url,
382 : false,
383 : &exchange_keys_cb,
384 : lookup);
385 8 : if (NULL == lookup->ko)
386 : {
387 0 : GNUNET_free (lookup->url);
388 0 : GNUNET_free (lookup);
389 0 : return;
390 : }
391 8 : GNUNET_CONTAINER_DLL_insert (gtc->lookup_head,
392 : gtc->lookup_tail,
393 : lookup);
394 : }
395 :
396 :
397 : /**
398 : * Stop waiting for exchange keys and return the partial candidate list.
399 : *
400 : * @param cls a `struct GetTemplateContext *`
401 : */
402 : static void
403 0 : exchange_lookup_timeout (void *cls)
404 : {
405 0 : struct GetTemplateContext *gtc = cls;
406 :
407 0 : gtc->timeout_task = NULL;
408 0 : while (NULL != gtc->lookup_head)
409 : {
410 0 : struct ExchangeLookup *lookup = gtc->lookup_head;
411 :
412 0 : GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
413 : gtc->lookup_tail,
414 : lookup);
415 0 : TMH_EXCHANGES_keys4exchange_cancel (lookup->ko);
416 0 : GNUNET_free (lookup->url);
417 0 : GNUNET_free (lookup);
418 : }
419 0 : resume_template_request (gtc);
420 0 : }
421 :
422 :
423 : /**
424 : * Cleanup a template request context.
425 : *
426 : * @param cls a `struct GetTemplateContext *`
427 : */
428 : static void
429 8 : get_template_cleanup (void *cls)
430 : {
431 8 : struct GetTemplateContext *gtc = cls;
432 :
433 8 : if (GNUNET_YES == gtc->suspended)
434 0 : GNUNET_CONTAINER_DLL_remove (gtc_head,
435 : gtc_tail,
436 : gtc);
437 8 : if (NULL != gtc->timeout_task)
438 0 : GNUNET_SCHEDULER_cancel (gtc->timeout_task);
439 8 : while (NULL != gtc->lookup_head)
440 : {
441 0 : struct ExchangeLookup *lookup = gtc->lookup_head;
442 :
443 0 : GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
444 : gtc->lookup_tail,
445 : lookup);
446 0 : TMH_EXCHANGES_keys4exchange_cancel (lookup->ko);
447 0 : GNUNET_free (lookup->url);
448 0 : GNUNET_free (lookup);
449 : }
450 8 : if (NULL != gtc->template_contract)
451 8 : json_decref (gtc->template_contract);
452 8 : if (NULL != gtc->editable_defaults)
453 1 : json_decref (gtc->editable_defaults);
454 8 : if (NULL != gtc->merchant)
455 8 : json_decref (gtc->merchant);
456 8 : if (NULL != gtc->exchange_candidates)
457 8 : json_decref (gtc->exchange_candidates);
458 8 : GNUNET_free (gtc);
459 8 : }
460 :
461 :
462 : void
463 20 : TMH_force_get_templates_ID_resume (void)
464 : {
465 20 : while (NULL != gtc_head)
466 : {
467 0 : struct GetTemplateContext *gtc = gtc_head;
468 :
469 0 : GNUNET_CONTAINER_DLL_remove (gtc_head,
470 : gtc_tail,
471 : gtc);
472 0 : gtc->suspended = GNUNET_SYSERR;
473 0 : MHD_resume_connection (gtc->connection);
474 0 : TALER_MHD_daemon_trigger ();
475 : }
476 20 : }
477 :
478 :
479 : /**
480 : * Context for building inventory template payloads.
481 : */
482 : struct InventoryPayloadContext
483 : {
484 : /**
485 : * Selected category IDs (as JSON array).
486 : */
487 : const json_t *selected_categories;
488 :
489 : /**
490 : * Selected product IDs (as JSON array) from contract_template.
491 : */
492 : const json_t *selected_products;
493 :
494 : /**
495 : * Whether all products are selected.
496 : */
497 : bool selected_all;
498 :
499 : /**
500 : * JSON array of products to build.
501 : */
502 : json_t *products;
503 :
504 : /**
505 : * JSON array of categories to build.
506 : */
507 : json_t *category_payload;
508 :
509 : /**
510 : * JSON array of units to build.
511 : */
512 : json_t *unit_payload;
513 :
514 : /**
515 : * Set of categories referenced by the products.
516 : */
517 : struct TMH_CategorySet category_set;
518 :
519 : /**
520 : * Set of unit identifiers referenced by the products.
521 : */
522 : struct TMH_UnitSet unit_set;
523 : };
524 :
525 :
526 : /**
527 : * Release resources associated with an inventory payload context.
528 : *
529 : * @param ipc inventory payload context
530 : */
531 : static void
532 4 : inventory_payload_cleanup (struct InventoryPayloadContext *ipc)
533 : {
534 4 : if (NULL != ipc->products)
535 0 : json_decref (ipc->products);
536 4 : if (NULL != ipc->category_payload)
537 0 : json_decref (ipc->category_payload);
538 4 : if (NULL != ipc->unit_payload)
539 0 : json_decref (ipc->unit_payload);
540 4 : GNUNET_free (ipc->category_set.ids);
541 4 : TMH_unit_set_clear (&ipc->unit_set);
542 4 : ipc->products = NULL;
543 4 : ipc->category_payload = NULL;
544 4 : ipc->unit_payload = NULL;
545 4 : ipc->category_set.len = 0;
546 4 : }
547 :
548 :
549 : /**
550 : * Add inventory product to JSON payload.
551 : *
552 : * @param cls inventory payload context
553 : * @param product_id product identifier
554 : * @param pd product details
555 : * @param num_categories number of categories
556 : * @param categories category IDs
557 : */
558 : static void
559 10 : add_inventory_product (
560 : void *cls,
561 : const char *product_id,
562 : const struct TALER_MERCHANTDB_InventoryProductDetails *pd,
563 : size_t num_categories,
564 : const uint64_t *categories)
565 : {
566 10 : struct InventoryPayloadContext *ipc = cls;
567 : json_t *jcategories;
568 : json_t *product;
569 : char remaining_stock_buf[64];
570 :
571 10 : jcategories = json_array ();
572 10 : GNUNET_assert (NULL != jcategories);
573 14 : for (size_t i = 0; i < num_categories; i++)
574 : {
575 : /* Adding per product category */
576 4 : TMH_category_set_add (&ipc->category_set,
577 4 : categories[i]);
578 4 : GNUNET_assert (0 ==
579 : json_array_append_new (jcategories,
580 : json_integer (categories[i])));
581 : }
582 10 : GNUNET_assert (0 < pd->price_array_length);
583 10 : TALER_MERCHANT_vk_format_fractional_string (
584 : TALER_MERCHANT_VK_STOCK,
585 10 : pd->remaining_stock,
586 10 : pd->remaining_stock_frac,
587 : sizeof (remaining_stock_buf),
588 : remaining_stock_buf);
589 10 : product = GNUNET_JSON_PACK (
590 : GNUNET_JSON_pack_string ("product_id",
591 : product_id),
592 : GNUNET_JSON_pack_string ("product_name",
593 : pd->product_name),
594 : GNUNET_JSON_pack_string ("description",
595 : pd->description),
596 : GNUNET_JSON_pack_allow_null (
597 : GNUNET_JSON_pack_object_incref ("description_i18n",
598 : pd->description_i18n)),
599 : GNUNET_JSON_pack_string ("unit",
600 : pd->unit),
601 : TALER_JSON_pack_amount_array ("unit_prices",
602 : pd->price_array_length,
603 : pd->price_array),
604 : GNUNET_JSON_pack_bool ("unit_allow_fraction",
605 : pd->allow_fractional_quantity),
606 : GNUNET_JSON_pack_uint64 ("unit_precision_level",
607 : pd->fractional_precision_level),
608 : GNUNET_JSON_pack_string ("remaining_stock",
609 : remaining_stock_buf),
610 : GNUNET_JSON_pack_array_steal ("categories",
611 : jcategories),
612 : GNUNET_JSON_pack_allow_null (
613 : GNUNET_JSON_pack_array_incref ("taxes",
614 : pd->taxes)),
615 : GNUNET_JSON_pack_allow_null (
616 : GNUNET_JSON_pack_string ("image_hash",
617 : pd->image_hash)));
618 :
619 : /* Adding per product unit */
620 10 : TMH_unit_set_add (&ipc->unit_set,
621 10 : pd->unit);
622 :
623 10 : GNUNET_assert (0 ==
624 : json_array_append_new (ipc->products,
625 : product));
626 10 : }
627 :
628 :
629 : /**
630 : * Add an inventory category to the payload if referenced.
631 : *
632 : * @param cls category payload context
633 : * @param category_id category identifier
634 : * @param category_name category name
635 : * @param category_name_i18n translated names
636 : * @param product_count number of products (unused)
637 : */
638 : static void
639 4 : add_inventory_category (void *cls,
640 : uint64_t category_id,
641 : const char *category_name,
642 : const json_t *category_name_i18n,
643 : uint64_t product_count)
644 : {
645 4 : struct InventoryPayloadContext *ipc = cls;
646 : json_t *category;
647 :
648 : (void) product_count;
649 4 : category = GNUNET_JSON_PACK (
650 : GNUNET_JSON_pack_uint64 ("category_id",
651 : category_id),
652 : GNUNET_JSON_pack_string ("category_name",
653 : category_name),
654 : GNUNET_JSON_pack_allow_null (
655 : GNUNET_JSON_pack_object_incref ("category_name_i18n",
656 : (json_t *) category_name_i18n)));
657 4 : GNUNET_assert (0 ==
658 : json_array_append_new (ipc->category_payload,
659 : category));
660 4 : }
661 :
662 :
663 : /**
664 : * Add an inventory unit to the payload if referenced and non-builtin.
665 : *
666 : * @param cls unit payload context
667 : * @param unit_serial unit identifier
668 : * @param ud unit details
669 : */
670 : static void
671 2 : add_inventory_unit (void *cls,
672 : uint64_t unit_serial,
673 : const struct TALER_MERCHANTDB_UnitDetails *ud)
674 : {
675 2 : struct InventoryPayloadContext *ipc = cls;
676 : json_t *unit;
677 :
678 : (void) unit_serial;
679 :
680 2 : unit = GNUNET_JSON_PACK (
681 : GNUNET_JSON_pack_string ("unit",
682 : ud->unit),
683 : GNUNET_JSON_pack_string ("unit_name_long",
684 : ud->unit_name_long),
685 : GNUNET_JSON_pack_allow_null (
686 : GNUNET_JSON_pack_object_incref ("unit_name_long_i18n",
687 : ud->unit_name_long_i18n)),
688 : GNUNET_JSON_pack_string ("unit_name_short",
689 : ud->unit_name_short),
690 : GNUNET_JSON_pack_allow_null (
691 : GNUNET_JSON_pack_object_incref ("unit_name_short_i18n",
692 : ud->unit_name_short_i18n)),
693 : GNUNET_JSON_pack_bool ("unit_allow_fraction",
694 : ud->unit_allow_fraction),
695 : GNUNET_JSON_pack_uint64 ("unit_precision_level",
696 : ud->unit_precision_level));
697 2 : GNUNET_assert (0 ==
698 : json_array_append_new (ipc->unit_payload,
699 : unit));
700 2 : }
701 :
702 :
703 : /**
704 : * Build wallet-facing payload for inventory templates.
705 : *
706 : * @param connection HTTP connection
707 : * @param mi merchant instance
708 : * @param tp template details
709 : * @return newly allocated template contract, NULL if an error was queued
710 : */
711 : static json_t *
712 4 : build_inventory_template_contract (
713 : struct MHD_Connection *connection,
714 : const struct TMH_MerchantInstance *mi,
715 : const struct TALER_MERCHANTDB_TemplateDetails *tp)
716 : {
717 : struct InventoryPayloadContext ipc;
718 4 : const char **product_ids = NULL;
719 4 : uint64_t *category_ids = NULL;
720 4 : size_t num_product_ids = 0;
721 4 : size_t num_category_ids = 0;
722 : json_t *inventory_payload;
723 : json_t *template_contract;
724 :
725 4 : memset (&ipc,
726 : 0,
727 : sizeof (ipc));
728 4 : ipc.products = json_array ();
729 : {
730 : struct GNUNET_JSON_Specification spec[] = {
731 4 : GNUNET_JSON_spec_mark_optional (
732 : GNUNET_JSON_spec_array_const ("selected_categories",
733 : &ipc.selected_categories),
734 : NULL),
735 4 : GNUNET_JSON_spec_mark_optional (
736 : GNUNET_JSON_spec_array_const ("selected_products",
737 : &ipc.selected_products),
738 : NULL),
739 4 : GNUNET_JSON_spec_mark_optional (
740 : GNUNET_JSON_spec_bool ("selected_all",
741 : &ipc.selected_all),
742 : NULL),
743 4 : GNUNET_JSON_spec_end ()
744 : };
745 : const char *err_name;
746 : unsigned int err_line;
747 :
748 4 : if (GNUNET_OK !=
749 4 : GNUNET_JSON_parse (tp->template_contract,
750 : spec,
751 : &err_name,
752 : &err_line))
753 : {
754 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
755 : "Invalid inventory template_contract for field %s\n",
756 : err_name);
757 0 : inventory_payload_cleanup (&ipc);
758 0 : (void) TALER_MHD_reply_with_error (
759 : connection,
760 : MHD_HTTP_INTERNAL_SERVER_ERROR,
761 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
762 : err_name);
763 0 : return NULL;
764 : }
765 : }
766 :
767 4 : if (! ipc.selected_all)
768 : {
769 4 : if (NULL != ipc.selected_products)
770 : {
771 : size_t max_ids;
772 :
773 4 : max_ids = json_array_size (ipc.selected_products);
774 4 : if (0 < max_ids)
775 4 : product_ids = GNUNET_new_array (max_ids,
776 : const char *);
777 12 : for (size_t i = 0; i < max_ids; i++)
778 : {
779 8 : const json_t *entry = json_array_get (ipc.selected_products,
780 : i);
781 :
782 8 : if (json_is_string (entry))
783 8 : product_ids[num_product_ids++] = json_string_value (entry);
784 : else
785 0 : GNUNET_break (0);
786 : }
787 : }
788 4 : if (NULL != ipc.selected_categories)
789 : {
790 : size_t max_categories;
791 :
792 2 : max_categories = json_array_size (ipc.selected_categories);
793 2 : if (0 < max_categories)
794 2 : category_ids = GNUNET_new_array (max_categories,
795 : uint64_t);
796 4 : for (size_t i = 0; i < max_categories; i++)
797 : {
798 2 : const json_t *entry = json_array_get (ipc.selected_categories,
799 : i);
800 :
801 4 : if (json_is_integer (entry) &&
802 2 : (0 < json_integer_value (entry)))
803 2 : category_ids[num_category_ids++]
804 2 : = (uint64_t) json_integer_value (entry);
805 : else
806 0 : GNUNET_break (0);
807 : }
808 : }
809 : }
810 :
811 4 : if (ipc.selected_all)
812 : {
813 : enum GNUNET_DB_QueryStatus qs;
814 :
815 0 : qs = TALER_MERCHANTDB_iterate_inventory_products (TMH_db,
816 0 : mi->settings.id,
817 : &add_inventory_product,
818 : &ipc);
819 0 : if (0 > qs)
820 : {
821 0 : GNUNET_break (0);
822 0 : inventory_payload_cleanup (&ipc);
823 0 : (void) TALER_MHD_reply_with_error (
824 : connection,
825 : MHD_HTTP_INTERNAL_SERVER_ERROR,
826 : TALER_EC_GENERIC_DB_FETCH_FAILED,
827 : "iterate_inventory_products");
828 0 : return NULL;
829 : }
830 : }
831 4 : else if ( (0 < num_product_ids) ||
832 : (0 < num_category_ids) )
833 : {
834 : enum GNUNET_DB_QueryStatus qs;
835 :
836 4 : qs = TALER_MERCHANTDB_iterate_inventory_products_filtered (
837 : TMH_db,
838 4 : mi->settings.id,
839 : product_ids,
840 : num_product_ids,
841 : category_ids,
842 : num_category_ids,
843 : &add_inventory_product,
844 : &ipc);
845 4 : if (0 > qs)
846 : {
847 0 : GNUNET_break (0);
848 0 : inventory_payload_cleanup (&ipc);
849 0 : GNUNET_free (product_ids);
850 0 : GNUNET_free (category_ids);
851 0 : (void) TALER_MHD_reply_with_error (
852 : connection,
853 : MHD_HTTP_INTERNAL_SERVER_ERROR,
854 : TALER_EC_GENERIC_DB_FETCH_FAILED,
855 : "iterate_inventory_products_filtered");
856 0 : return NULL;
857 : }
858 : }
859 4 : GNUNET_free (product_ids);
860 4 : GNUNET_free (category_ids);
861 :
862 4 : ipc.category_payload = json_array ();
863 4 : GNUNET_assert (NULL != ipc.category_payload);
864 4 : if (0 < ipc.category_set.len)
865 : {
866 : enum GNUNET_DB_QueryStatus qs;
867 :
868 2 : qs = TALER_MERCHANTDB_iterate_categories_by_ids (
869 : TMH_db,
870 2 : mi->settings.id,
871 2 : ipc.category_set.ids,
872 2 : ipc.category_set.len,
873 : &add_inventory_category,
874 : &ipc);
875 2 : if (0 > qs)
876 : {
877 0 : GNUNET_break (0);
878 0 : inventory_payload_cleanup (&ipc);
879 0 : (void) TALER_MHD_reply_with_error (
880 : connection,
881 : MHD_HTTP_INTERNAL_SERVER_ERROR,
882 : TALER_EC_GENERIC_DB_FETCH_FAILED,
883 : "iterate_categories_by_ids");
884 0 : return NULL;
885 : }
886 : }
887 :
888 4 : ipc.unit_payload = json_array ();
889 4 : GNUNET_assert (NULL != ipc.unit_payload);
890 4 : if (0 < ipc.unit_set.len)
891 : {
892 : enum GNUNET_DB_QueryStatus qs;
893 :
894 4 : qs = TALER_MERCHANTDB_iterate_custom_units_by_names (
895 : TMH_db,
896 4 : mi->settings.id,
897 4 : (const char *const *) ipc.unit_set.units,
898 4 : ipc.unit_set.len,
899 : &add_inventory_unit,
900 : &ipc);
901 4 : if (0 > qs)
902 : {
903 0 : GNUNET_break (0);
904 0 : inventory_payload_cleanup (&ipc);
905 0 : (void) TALER_MHD_reply_with_error (
906 : connection,
907 : MHD_HTTP_INTERNAL_SERVER_ERROR,
908 : TALER_EC_GENERIC_DB_FETCH_FAILED,
909 : "iterate_custom_units_by_names");
910 0 : return NULL;
911 : }
912 : }
913 :
914 4 : inventory_payload = GNUNET_JSON_PACK (
915 : GNUNET_JSON_pack_array_steal ("products",
916 : ipc.products),
917 : GNUNET_JSON_pack_array_steal ("categories",
918 : ipc.category_payload),
919 : GNUNET_JSON_pack_array_steal ("units",
920 : ipc.unit_payload));
921 4 : ipc.products = NULL;
922 4 : ipc.category_payload = NULL;
923 4 : ipc.unit_payload = NULL;
924 :
925 4 : template_contract = json_deep_copy (tp->template_contract);
926 4 : GNUNET_assert (NULL != template_contract);
927 : /* remove internal fields */
928 4 : (void) json_object_del (template_contract,
929 : "selected_categories");
930 4 : (void) json_object_del (template_contract,
931 : "selected_products");
932 4 : (void) json_object_del (template_contract,
933 : "selected_all");
934 : /* add inventory data */
935 4 : GNUNET_assert (0 ==
936 : json_object_set_new (template_contract,
937 : "inventory_payload",
938 : inventory_payload));
939 4 : inventory_payload_cleanup (&ipc);
940 4 : return template_contract;
941 : }
942 :
943 :
944 : enum MHD_Result
945 16 : TMH_get_templates_ID (
946 : const struct TMH_RequestHandler *rh,
947 : struct MHD_Connection *connection,
948 : struct TMH_HandlerContext *hc)
949 : {
950 16 : struct GetTemplateContext *gtc = hc->ctx;
951 16 : struct TMH_MerchantInstance *mi = hc->instance;
952 :
953 : (void) rh;
954 16 : GNUNET_assert (NULL != mi);
955 16 : if (NULL == gtc)
956 : {
957 8 : struct TALER_MERCHANTDB_TemplateDetails tp = { 0 };
958 : enum GNUNET_DB_QueryStatus qs;
959 :
960 8 : gtc = GNUNET_new (struct GetTemplateContext);
961 8 : gtc->hc = hc;
962 8 : gtc->connection = connection;
963 8 : gtc->exchange_candidates = json_array ();
964 8 : GNUNET_assert (NULL != gtc->exchange_candidates);
965 8 : gtc->merchant = TMH_instance_metadata_to_json (mi);
966 8 : GNUNET_assert (NULL != gtc->merchant);
967 8 : gtc->merchant_pub = mi->merchant_pub;
968 8 : hc->ctx = gtc;
969 8 : hc->cc = &get_template_cleanup;
970 :
971 8 : qs = TALER_MERCHANTDB_get_template (TMH_db,
972 8 : mi->settings.id,
973 8 : hc->infix,
974 : &tp);
975 8 : if (0 > qs)
976 : {
977 0 : GNUNET_break (0);
978 8 : return TALER_MHD_reply_with_error (
979 : connection,
980 : MHD_HTTP_INTERNAL_SERVER_ERROR,
981 : TALER_EC_GENERIC_DB_FETCH_FAILED,
982 : "get_template");
983 : }
984 8 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
985 : {
986 0 : return TALER_MHD_reply_with_error (
987 : connection,
988 : MHD_HTTP_NOT_FOUND,
989 : TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
990 0 : hc->infix);
991 : }
992 8 : if (NULL != tp.editable_defaults)
993 1 : gtc->editable_defaults = json_incref (tp.editable_defaults);
994 8 : switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract))
995 : {
996 4 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
997 4 : gtc->template_contract = build_inventory_template_contract (connection,
998 : mi,
999 : &tp);
1000 4 : break;
1001 4 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
1002 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
1003 4 : gtc->template_contract = json_incref (tp.template_contract);
1004 4 : break;
1005 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
1006 0 : GNUNET_break_op (0);
1007 0 : (void) TALER_MHD_reply_with_error (
1008 : connection,
1009 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1010 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1011 : "template_type");
1012 0 : break;
1013 : }
1014 8 : TALER_MERCHANTDB_template_details_free (&tp);
1015 8 : if (NULL == gtc->template_contract)
1016 0 : return MHD_YES;
1017 :
1018 8 : TMH_exchange_get_trusted (&start_exchange_lookup,
1019 : gtc);
1020 8 : if (NULL != gtc->lookup_head)
1021 : {
1022 8 : gtc->timeout_task = GNUNET_SCHEDULER_add_delayed (
1023 : MAX_KEYS_WAIT,
1024 : &exchange_lookup_timeout,
1025 : gtc);
1026 8 : GNUNET_CONTAINER_DLL_insert (gtc_head,
1027 : gtc_tail,
1028 : gtc);
1029 8 : gtc->suspended = GNUNET_YES;
1030 8 : MHD_suspend_connection (connection);
1031 8 : return MHD_YES;
1032 : }
1033 : }
1034 8 : if (GNUNET_SYSERR == gtc->suspended)
1035 0 : return MHD_NO;
1036 8 : return TALER_MHD_REPLY_JSON_PACK (
1037 : connection,
1038 : MHD_HTTP_OK,
1039 : GNUNET_JSON_pack_object_incref ("template_contract",
1040 : gtc->template_contract),
1041 : GNUNET_JSON_pack_allow_null (
1042 : GNUNET_JSON_pack_object_incref ("editable_defaults",
1043 : gtc->editable_defaults)),
1044 : GNUNET_JSON_pack_allow_null (
1045 : GNUNET_JSON_pack_string ("required_currency",
1046 : get_required_currency (
1047 : gtc->template_contract))),
1048 : GNUNET_JSON_pack_object_incref ("merchant",
1049 : gtc->merchant),
1050 : GNUNET_JSON_pack_data_auto ("merchant_pub",
1051 : >c->merchant_pub),
1052 : GNUNET_JSON_pack_array_incref ("exchange_candidates",
1053 : gtc->exchange_candidates));
1054 : }
1055 :
1056 :
1057 : /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */
|