Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2022-2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Affero General Public License as
7 : published by the Free Software Foundation; either version 3,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 :
20 : /**
21 : * @file src/backend/taler-merchant-httpd_post-templates-TEMPLATE_ID.c
22 : * @brief implementing POST /using-templates request handling
23 : * @author Priscilla HUANG
24 : * @author Christian Grothoff
25 : */
26 : #include "platform.h"
27 : #include "taler-merchant-httpd_exchanges.h"
28 : #include "taler-merchant-httpd_post-templates-TEMPLATE_ID.h"
29 : #include "taler-merchant-httpd_post-private-orders.h"
30 : #include "taler-merchant-httpd_helper.h"
31 : #include "taler-merchant-httpd_get-exchanges.h"
32 : #include "taler/taler_merchant_util.h"
33 : #include <taler/taler_json_lib.h>
34 : #include <regex.h>
35 : #include "merchant-database/get_product.h"
36 : #include "merchant-database/get_template.h"
37 :
38 :
39 : /**
40 : * Maximum number of entries we accept in the @e inventory_selection
41 : * array of a request. Each entry costs us one database round-trip
42 : * and a full copy of the product details (including the base64-encoded
43 : * product image), so this must be bounded independently of the maximum
44 : * upload size.
45 : */
46 : #define MAX_INVENTORY_SELECTION 1024
47 :
48 :
49 : /**
50 : * Amount the client chose for one of the choices of a paivana template.
51 : */
52 : struct PaivanaChoiceAmount
53 : {
54 : /**
55 : * Index into the @e choices array of the template contract.
56 : */
57 : uint32_t choice_index;
58 :
59 : /**
60 : * Amount to use for that choice, excluding any tip.
61 : */
62 : struct TALER_Amount amount;
63 : };
64 :
65 :
66 : /**
67 : * Item selected from inventory_selection.
68 : */
69 : struct InventoryTemplateItemContext
70 : {
71 : /**
72 : * Product ID as referenced in inventory.
73 : */
74 : const char *product_id;
75 :
76 : /**
77 : * Unit quantity string as provided by the client.
78 : */
79 : const char *unit_quantity;
80 :
81 : /**
82 : * Parsed integer quantity.
83 : */
84 : uint64_t quantity_value;
85 :
86 : /**
87 : * Parsed fractional quantity.
88 : */
89 : uint32_t quantity_frac;
90 :
91 : /**
92 : * Product details from the DB (includes price array).
93 : */
94 : struct TALER_MERCHANTDB_ProductDetails pd;
95 :
96 : /**
97 : * Categories referenced by the product.
98 : */
99 : uint64_t *categories;
100 :
101 : /**
102 : * Length of @e categories.
103 : */
104 : size_t num_categories;
105 : };
106 :
107 :
108 : /**
109 : * Our context.
110 : */
111 : enum UsePhase
112 : {
113 : /**
114 : * Parse request payload into context fields.
115 : */
116 : USE_PHASE_PARSE_REQUEST,
117 :
118 : /**
119 : * Fetch template details from the database.
120 : */
121 : USE_PHASE_LOOKUP_TEMPLATE,
122 :
123 : /**
124 : * Parse template.
125 : */
126 : USE_PHASE_PARSE_TEMPLATE,
127 :
128 : /**
129 : * Load additional details (like products and
130 : * categories) needed for verification and
131 : * price computation.
132 : */
133 : USE_PHASE_DB_FETCH,
134 :
135 : /**
136 : * Validate request and template compatibility.
137 : */
138 : USE_PHASE_VERIFY,
139 :
140 : /**
141 : * Compute price of the order.
142 : */
143 : USE_PHASE_COMPUTE_PRICE,
144 :
145 : /**
146 : * Handle tip.
147 : */
148 : USE_PHASE_CHECK_TIP,
149 :
150 : /**
151 : * Check if client-supplied total amount matches
152 : * our calculation (if we did any).
153 : */
154 : USE_PHASE_CHECK_TOTAL,
155 :
156 : /**
157 : * Construct the internal order request body.
158 : */
159 : USE_PHASE_CREATE_ORDER,
160 :
161 : /**
162 : * Submit the order to the shared order handler.
163 : */
164 : USE_PHASE_SUBMIT_ORDER,
165 :
166 : /**
167 : * Finished successfully with MHD_YES.
168 : */
169 : USE_PHASE_FINISHED_MHD_YES,
170 :
171 : /**
172 : * Finished with MHD_NO.
173 : */
174 : USE_PHASE_FINISHED_MHD_NO
175 : };
176 :
177 : struct UseContext
178 : {
179 : /**
180 : * Context for our handler.
181 : */
182 : struct TMH_HandlerContext *hc;
183 :
184 : /**
185 : * Internal handler context we are passing into the
186 : * POST /private/orders handler.
187 : */
188 : struct TMH_HandlerContext ihc;
189 :
190 : /**
191 : * Phase we are currently in.
192 : */
193 : enum UsePhase phase;
194 :
195 : /**
196 : * Template type from the contract.
197 : */
198 : enum TALER_MERCHANT_TemplateType template_type;
199 :
200 : /**
201 : * Information set in the #USE_PHASE_PARSE_REQUEST phase.
202 : */
203 : struct
204 : {
205 : /**
206 : * Summary override from request, if any.
207 : */
208 : const char *summary;
209 :
210 : /**
211 : * Amount provided by the client.
212 : */
213 : struct TALER_Amount amount;
214 :
215 : /**
216 : * Tip provided by the client.
217 : */
218 : struct TALER_Amount tip;
219 :
220 : /**
221 : * True if @e amount was not provided.
222 : */
223 : bool no_amount;
224 :
225 : /**
226 : * True if @e tip was not provided.
227 : */
228 : bool no_tip;
229 :
230 : /**
231 : * Parsed fields for inventory templates.
232 : */
233 : struct
234 : {
235 : /**
236 : * Selected products from inventory_selection.
237 : */
238 : struct InventoryTemplateItemContext *items;
239 :
240 : /**
241 : * Length of @e items.
242 : */
243 : unsigned int items_len;
244 :
245 : } inventory;
246 :
247 : /**
248 : * Request details if this is a paivana instantiation.
249 : */
250 : struct
251 : {
252 :
253 : /**
254 : * Target website for the request.
255 : */
256 : const char *website;
257 :
258 : /**
259 : * Unique client identifier, consisting of
260 : * current time, "-", and the hash of a nonce,
261 : * the website and the current time.
262 : */
263 : const char *paivana_id;
264 :
265 : /**
266 : * Amounts the client picked for those choices of the
267 : * template that allow the amount to be edited.
268 : */
269 : struct PaivanaChoiceAmount *choice_amounts;
270 :
271 : /**
272 : * Length of the @e choice_amounts array.
273 : */
274 : unsigned int choice_amounts_len;
275 :
276 : } paivana;
277 :
278 : } parse_request;
279 :
280 : /**
281 : * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase.
282 : */
283 : struct
284 : {
285 :
286 : /**
287 : * Our template details from the DB.
288 : */
289 : struct TALER_MERCHANTDB_TemplateDetails etp;
290 :
291 : } lookup_template;
292 :
293 : /**
294 : * Information set in the #USE_PHASE_PARSE_TEMPLATE phase.
295 : */
296 : struct TALER_MERCHANT_TemplateContract template_contract;
297 :
298 : /**
299 : * Information set in the #USE_PHASE_COMPUTE_PRICE phase.
300 : */
301 : struct
302 : {
303 :
304 : /**
305 : * Per-currency totals across selected products (without tips).
306 : */
307 : struct TALER_Amount *totals;
308 :
309 : /**
310 : * Length of @e totals.
311 : */
312 : unsigned int totals_len;
313 :
314 : /**
315 : * Array of payment choices, used with Paviana.
316 : */
317 : json_t *choices;
318 :
319 : } compute_price;
320 :
321 : };
322 :
323 :
324 : /**
325 : * Clean up inventory items.
326 : *
327 : * @param items_len length of @a items
328 : * @param[in] items item array to free
329 : */
330 : static void
331 18 : cleanup_inventory_items (
332 : unsigned int items_len,
333 : struct InventoryTemplateItemContext items[static items_len])
334 18 : {
335 44 : for (unsigned int i = 0; i < items_len; i++)
336 : {
337 26 : struct InventoryTemplateItemContext *item = &items[i];
338 :
339 26 : TALER_MERCHANTDB_product_details_free (&item->pd);
340 26 : GNUNET_free (item->categories);
341 : }
342 18 : GNUNET_free (items);
343 18 : }
344 :
345 :
346 : /**
347 : * Clean up a `struct UseContext *`
348 : *
349 : * @param[in] cls a `struct UseContext *`
350 : */
351 : static void
352 46 : cleanup_use_context (void *cls)
353 : {
354 46 : struct UseContext *uc = cls;
355 :
356 46 : TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp);
357 46 : if (NULL !=
358 46 : uc->parse_request.inventory.items)
359 18 : cleanup_inventory_items (uc->parse_request.inventory.items_len,
360 : uc->parse_request.inventory.items);
361 46 : GNUNET_array_grow (uc->parse_request.paivana.choice_amounts,
362 : uc->parse_request.paivana.choice_amounts_len,
363 : 0);
364 46 : TALER_MERCHANT_template_contract_free (&uc->template_contract);
365 46 : GNUNET_free (uc->compute_price.totals);
366 46 : uc->compute_price.totals_len = 0;
367 46 : json_decref (uc->compute_price.choices);
368 46 : if (NULL != uc->ihc.cc)
369 19 : uc->ihc.cc (uc->ihc.ctx);
370 46 : GNUNET_free (uc->ihc.infix);
371 46 : json_decref (uc->ihc.request_body);
372 46 : GNUNET_free (uc);
373 46 : }
374 :
375 :
376 : /**
377 : * Finalize a template use request.
378 : *
379 : * @param[in,out] uc use context
380 : * @param ret handler return value
381 : */
382 : static void
383 27 : use_finalize (struct UseContext *uc,
384 : enum MHD_Result ret)
385 : {
386 27 : uc->phase = (MHD_YES == ret)
387 : ? USE_PHASE_FINISHED_MHD_YES
388 27 : : USE_PHASE_FINISHED_MHD_NO;
389 27 : }
390 :
391 :
392 : /**
393 : * Finalize after JSON parsing result.
394 : *
395 : * @param[in,out] uc use context
396 : * @param res parse result
397 : */
398 : static void
399 0 : use_finalize_parse (struct UseContext *uc,
400 : enum GNUNET_GenericReturnValue res)
401 : {
402 0 : GNUNET_assert (GNUNET_OK != res);
403 0 : use_finalize (uc,
404 : (GNUNET_NO == res)
405 : ? MHD_YES
406 : : MHD_NO);
407 0 : }
408 :
409 :
410 : /**
411 : * Reply with error and finalize the request.
412 : *
413 : * @param[in,out] uc use context
414 : * @param http_status HTTP status code
415 : * @param ec error code
416 : * @param detail error detail
417 : */
418 : static void
419 27 : use_reply_with_error (struct UseContext *uc,
420 : unsigned int http_status,
421 : enum TALER_ErrorCode ec,
422 : const char *detail)
423 : {
424 : enum MHD_Result mret;
425 :
426 27 : mret = TALER_MHD_reply_with_error (uc->hc->connection,
427 : http_status,
428 : ec,
429 : detail);
430 27 : use_finalize (uc,
431 : mret);
432 27 : }
433 :
434 :
435 : /* ***************** USE_PHASE_PARSE_REQUEST **************** */
436 :
437 : /**
438 : * Parse request data for inventory templates.
439 : *
440 : * @param[in,out] uc use context
441 : * @return #GNUNET_OK on success
442 : */
443 : static enum GNUNET_GenericReturnValue
444 20 : parse_using_templates_inventory_request (
445 : struct UseContext *uc)
446 : {
447 : const json_t *inventory_selection;
448 : struct GNUNET_JSON_Specification spec[] = {
449 20 : GNUNET_JSON_spec_array_const ("inventory_selection",
450 : &inventory_selection),
451 20 : GNUNET_JSON_spec_end ()
452 : };
453 : enum GNUNET_GenericReturnValue res;
454 :
455 20 : GNUNET_assert (NULL == uc->ihc.request_body);
456 20 : res = TALER_MHD_parse_json_data (uc->hc->connection,
457 20 : uc->hc->request_body,
458 : spec);
459 20 : if (GNUNET_OK != res)
460 : {
461 0 : GNUNET_break_op (0);
462 0 : use_finalize_parse (uc,
463 : res);
464 0 : return GNUNET_SYSERR;
465 : }
466 :
467 20 : if ( (! uc->parse_request.no_amount) &&
468 20 : (! TMH_test_exchange_configured_for_currency (
469 20 : uc->parse_request.amount.currency)) )
470 : {
471 0 : GNUNET_break_op (0);
472 0 : use_reply_with_error (uc,
473 : MHD_HTTP_CONFLICT,
474 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
475 : "Currency is not supported by backend");
476 0 : return GNUNET_SYSERR;
477 : }
478 :
479 20 : if (MAX_INVENTORY_SELECTION < json_array_size (inventory_selection))
480 : {
481 0 : GNUNET_break_op (0);
482 0 : use_reply_with_error (uc,
483 : MHD_HTTP_BAD_REQUEST,
484 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
485 : "inventory_selection (too many entries)");
486 0 : return GNUNET_SYSERR;
487 : }
488 46 : for (size_t i = 0; i < json_array_size (inventory_selection); i++)
489 : {
490 26 : struct InventoryTemplateItemContext item = { 0 };
491 : struct GNUNET_JSON_Specification ispec[] = {
492 26 : TALER_JSON_spec_slug ("product_id",
493 : &item.product_id),
494 26 : GNUNET_JSON_spec_string ("quantity",
495 : &item.unit_quantity),
496 26 : GNUNET_JSON_spec_end ()
497 : };
498 : const char *err_name;
499 : unsigned int err_line;
500 :
501 26 : res = GNUNET_JSON_parse (json_array_get (inventory_selection,
502 : i),
503 : ispec,
504 : &err_name,
505 : &err_line);
506 26 : if (GNUNET_OK != res)
507 : {
508 0 : GNUNET_break_op (0);
509 0 : use_reply_with_error (uc,
510 : MHD_HTTP_BAD_REQUEST,
511 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
512 : "inventory_selection");
513 0 : return GNUNET_SYSERR;
514 : }
515 :
516 26 : GNUNET_array_append (uc->parse_request.inventory.items,
517 : uc->parse_request.inventory.items_len,
518 : item);
519 : }
520 20 : return GNUNET_OK;
521 : }
522 :
523 :
524 : /**
525 : * Parse request data for paivana templates.
526 : *
527 : * @param[in,out] uc use context
528 : * @return #GNUNET_OK on success
529 : */
530 : static enum GNUNET_GenericReturnValue
531 8 : parse_using_templates_paivana_request (
532 : struct UseContext *uc)
533 : {
534 8 : const json_t *choice_amounts = NULL;
535 : struct GNUNET_JSON_Specification spec[] = {
536 8 : TALER_JSON_spec_web_url ("website",
537 : &uc->parse_request.paivana.website),
538 8 : GNUNET_JSON_spec_string ("paivana_id",
539 : &uc->parse_request.paivana.paivana_id),
540 8 : GNUNET_JSON_spec_mark_optional (
541 : GNUNET_JSON_spec_array_const ("choice_amounts",
542 : &choice_amounts),
543 : NULL),
544 8 : GNUNET_JSON_spec_end ()
545 : };
546 : enum GNUNET_GenericReturnValue res;
547 : unsigned long long tv;
548 : const char *dash;
549 :
550 8 : GNUNET_assert (NULL == uc->ihc.request_body);
551 8 : res = TALER_MHD_parse_json_data (uc->hc->connection,
552 8 : uc->hc->request_body,
553 : spec);
554 8 : if (GNUNET_OK != res)
555 : {
556 0 : GNUNET_break_op (0);
557 0 : use_finalize_parse (uc,
558 : res);
559 0 : return GNUNET_SYSERR;
560 : }
561 8 : if (NULL != choice_amounts)
562 : {
563 3 : if (! uc->parse_request.no_amount)
564 : {
565 : /* 'amount' is the shorthand for a template with a single
566 : editable choice; using both is ambiguous. */
567 1 : GNUNET_break_op (0);
568 1 : use_reply_with_error (
569 : uc,
570 : MHD_HTTP_CONFLICT,
571 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
572 : "amount and choice_amounts are mutually exclusive");
573 1 : return GNUNET_SYSERR;
574 : }
575 4 : for (size_t i = 0; i < json_array_size (choice_amounts); i++)
576 : {
577 : struct PaivanaChoiceAmount ca;
578 : struct GNUNET_JSON_Specification ispec[] = {
579 2 : GNUNET_JSON_spec_uint32 ("choice_index",
580 : &ca.choice_index),
581 2 : TALER_JSON_spec_amount_any ("amount",
582 : &ca.amount),
583 2 : GNUNET_JSON_spec_end ()
584 : };
585 : const char *err_name;
586 : unsigned int err_line;
587 :
588 2 : if (GNUNET_OK !=
589 2 : GNUNET_JSON_parse (json_array_get (choice_amounts,
590 : i),
591 : ispec,
592 : &err_name,
593 : &err_line))
594 : {
595 0 : GNUNET_break_op (0);
596 0 : use_reply_with_error (uc,
597 : MHD_HTTP_BAD_REQUEST,
598 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
599 : "choice_amounts");
600 0 : return GNUNET_SYSERR;
601 : }
602 2 : for (unsigned int j = 0;
603 2 : j < uc->parse_request.paivana.choice_amounts_len;
604 0 : j++)
605 : {
606 0 : if (uc->parse_request.paivana.choice_amounts[j].choice_index !=
607 0 : ca.choice_index)
608 0 : continue;
609 0 : GNUNET_break_op (0);
610 0 : use_reply_with_error (uc,
611 : MHD_HTTP_BAD_REQUEST,
612 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
613 : "choice_amounts::choice_index is not unique");
614 0 : return GNUNET_SYSERR;
615 : }
616 : /* The currency does not need to be checked against our
617 : configuration here: it must match the currency of the
618 : choice in the template, which the merchant picked. */
619 2 : GNUNET_array_append (uc->parse_request.paivana.choice_amounts,
620 : uc->parse_request.paivana.choice_amounts_len,
621 : ca);
622 : }
623 : }
624 7 : if (! TALER_is_session_id (uc->parse_request.paivana.paivana_id))
625 : {
626 : /* The Paivana ID becomes the session ID of the order, and thus
627 : ends up as a path component of the "taler://pay/" URI; the
628 : base64url decoding below is too lenient to ensure this. */
629 0 : GNUNET_break_op (0);
630 0 : use_reply_with_error (uc,
631 : MHD_HTTP_BAD_REQUEST,
632 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
633 : "paivana_id");
634 0 : return GNUNET_SYSERR;
635 : }
636 7 : if (1 !=
637 7 : sscanf (uc->parse_request.paivana.paivana_id,
638 : "%llu-",
639 : &tv))
640 : {
641 0 : GNUNET_break_op (0);
642 0 : use_reply_with_error (uc,
643 : MHD_HTTP_BAD_REQUEST,
644 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
645 : "paivana_id");
646 0 : return GNUNET_SYSERR;
647 : }
648 7 : dash = strchr (uc->parse_request.paivana.paivana_id,
649 : '-');
650 7 : if (NULL == dash)
651 : {
652 0 : GNUNET_break_op (0);
653 0 : use_reply_with_error (uc,
654 : MHD_HTTP_BAD_REQUEST,
655 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
656 : "paivana_id");
657 0 : return GNUNET_SYSERR;
658 : }
659 : {
660 : size_t olen;
661 7 : void *out = NULL;
662 :
663 7 : olen = GNUNET_STRINGS_base64url_decode (dash + 1,
664 : strlen (dash + 1),
665 : &out);
666 7 : GNUNET_free (out);
667 7 : if (sizeof (struct GNUNET_ShortHashCode) != olen)
668 : {
669 0 : GNUNET_break_op (0);
670 0 : use_reply_with_error (uc,
671 : MHD_HTTP_BAD_REQUEST,
672 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
673 : "paivana_id");
674 0 : return GNUNET_SYSERR;
675 : }
676 : }
677 7 : return GNUNET_OK;
678 : }
679 :
680 :
681 : /**
682 : * Main function for the #USE_PHASE_PARSE_REQUEST.
683 : *
684 : * @param[in,out] uc context to update
685 : */
686 : static void
687 46 : handle_phase_parse_request (
688 : struct UseContext *uc)
689 : {
690 46 : const char *template_type = NULL;
691 : struct GNUNET_JSON_Specification spec[] = {
692 46 : GNUNET_JSON_spec_mark_optional (
693 : GNUNET_JSON_spec_string ("template_type",
694 : &template_type),
695 : NULL),
696 46 : GNUNET_JSON_spec_mark_optional (
697 : TALER_JSON_spec_amount_any ("tip",
698 : &uc->parse_request.tip),
699 : &uc->parse_request.no_tip),
700 46 : GNUNET_JSON_spec_mark_optional (
701 : GNUNET_JSON_spec_string ("summary",
702 : &uc->parse_request.summary),
703 : NULL),
704 46 : GNUNET_JSON_spec_mark_optional (
705 : TALER_JSON_spec_amount_any ("amount",
706 : &uc->parse_request.amount),
707 : &uc->parse_request.no_amount),
708 46 : GNUNET_JSON_spec_end ()
709 : };
710 : enum GNUNET_GenericReturnValue res;
711 :
712 46 : res = TALER_MHD_parse_json_data (uc->hc->connection,
713 46 : uc->hc->request_body,
714 : spec);
715 46 : if (GNUNET_OK != res)
716 : {
717 0 : GNUNET_break_op (0);
718 0 : use_finalize_parse (uc,
719 : res);
720 46 : return;
721 : }
722 46 : if (NULL == template_type)
723 0 : template_type = "fixed-order";
724 : uc->template_type
725 46 : = TALER_MERCHANT_template_type_from_string (
726 : template_type);
727 46 : switch (uc->template_type)
728 : {
729 18 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
730 : /* nothig left to do */
731 18 : uc->phase++;
732 18 : return;
733 8 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
734 8 : res = parse_using_templates_paivana_request (uc);
735 8 : if (GNUNET_OK == res)
736 7 : uc->phase++;
737 8 : return;
738 20 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
739 20 : res = parse_using_templates_inventory_request (uc);
740 20 : if (GNUNET_OK == res)
741 20 : uc->phase++;
742 20 : return;
743 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
744 0 : break;
745 : }
746 0 : GNUNET_break (0);
747 0 : use_reply_with_error (
748 : uc,
749 : MHD_HTTP_BAD_REQUEST,
750 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
751 : "template_type");
752 : }
753 :
754 :
755 : /* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */
756 :
757 : /**
758 : * Main function for the #USE_PHASE_LOOKUP_TEMPLATE.
759 : *
760 : * @param[in,out] uc context to update
761 : */
762 : static void
763 45 : handle_phase_lookup_template (
764 : struct UseContext *uc)
765 : {
766 45 : struct TMH_MerchantInstance *mi = uc->hc->instance;
767 45 : const char *template_id = uc->hc->infix;
768 : enum GNUNET_DB_QueryStatus qs;
769 :
770 45 : qs = TALER_MERCHANTDB_get_template (TMH_db,
771 45 : mi->settings.id,
772 : template_id,
773 : &uc->lookup_template.etp);
774 45 : switch (qs)
775 : {
776 0 : case GNUNET_DB_STATUS_HARD_ERROR:
777 : /* Clean up and fail hard */
778 0 : GNUNET_break (0);
779 0 : use_reply_with_error (uc,
780 : MHD_HTTP_INTERNAL_SERVER_ERROR,
781 : TALER_EC_GENERIC_DB_FETCH_FAILED,
782 : "get_template");
783 0 : return;
784 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
785 : /* this should be impossible (single select) */
786 0 : GNUNET_break (0);
787 0 : use_reply_with_error (uc,
788 : MHD_HTTP_INTERNAL_SERVER_ERROR,
789 : TALER_EC_GENERIC_DB_FETCH_FAILED,
790 : "get_template");
791 0 : return;
792 2 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
793 : /* template not found! */
794 2 : use_reply_with_error (uc,
795 : MHD_HTTP_NOT_FOUND,
796 : TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
797 : template_id);
798 2 : return;
799 43 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
800 : /* all good */
801 43 : break;
802 : }
803 43 : if (uc->template_type !=
804 43 : TALER_MERCHANT_template_type_from_contract (
805 43 : uc->lookup_template.etp.template_contract))
806 : {
807 0 : GNUNET_break_op (0);
808 0 : use_reply_with_error (
809 : uc,
810 : MHD_HTTP_CONFLICT,
811 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE,
812 : "template_contract has different type");
813 0 : return;
814 : }
815 43 : uc->phase++;
816 : }
817 :
818 :
819 : /* ***************** USE_PHASE_PARSE_TEMPLATE **************** */
820 :
821 :
822 : /**
823 : * Parse template.
824 : *
825 : * @param[in,out] uc use context
826 : */
827 : static void
828 43 : handle_phase_template_contract (struct UseContext *uc)
829 : {
830 : const char *err_name;
831 : enum GNUNET_GenericReturnValue res;
832 :
833 43 : res = TALER_MERCHANT_template_contract_parse (
834 43 : uc->lookup_template.etp.template_contract,
835 : &uc->template_contract,
836 : &err_name);
837 43 : if (GNUNET_OK != res)
838 : {
839 0 : GNUNET_break (0);
840 0 : use_reply_with_error (uc,
841 : MHD_HTTP_INTERNAL_SERVER_ERROR,
842 : TALER_EC_GENERIC_DB_FETCH_FAILED,
843 : err_name);
844 0 : return;
845 : }
846 43 : uc->phase++;
847 : }
848 :
849 :
850 : /* ***************** USE_PHASE_DB_FETCH **************** */
851 :
852 : /**
853 : * Fetch DB data for inventory templates.
854 : *
855 : * @param[in,out] uc use context
856 : */
857 : static void
858 43 : handle_phase_db_fetch (struct UseContext *uc)
859 : {
860 43 : struct TMH_MerchantInstance *mi = uc->hc->instance;
861 :
862 43 : switch (uc->template_type)
863 : {
864 16 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
865 16 : uc->phase++;
866 16 : return;
867 7 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
868 7 : uc->phase++;
869 7 : return;
870 20 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
871 20 : break;
872 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
873 0 : GNUNET_assert (0);
874 : }
875 :
876 20 : for (unsigned int i = 0;
877 44 : i < uc->parse_request.inventory.items_len;
878 24 : i++)
879 : {
880 26 : struct InventoryTemplateItemContext *item =
881 26 : &uc->parse_request.inventory.items[i];
882 : enum GNUNET_DB_QueryStatus qs;
883 :
884 26 : qs = TALER_MERCHANTDB_get_product (TMH_db,
885 26 : mi->settings.id,
886 : item->product_id,
887 : &item->pd,
888 : &item->num_categories,
889 : &item->categories);
890 26 : switch (qs)
891 : {
892 0 : case GNUNET_DB_STATUS_HARD_ERROR:
893 0 : GNUNET_break (0);
894 0 : use_reply_with_error (uc,
895 : MHD_HTTP_INTERNAL_SERVER_ERROR,
896 : TALER_EC_GENERIC_DB_FETCH_FAILED,
897 : "get_product");
898 0 : return;
899 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
900 0 : GNUNET_break (0);
901 0 : use_reply_with_error (uc,
902 : MHD_HTTP_INTERNAL_SERVER_ERROR,
903 : TALER_EC_GENERIC_DB_FETCH_FAILED,
904 : "get_product");
905 0 : return;
906 2 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
907 2 : use_reply_with_error (uc,
908 : MHD_HTTP_NOT_FOUND,
909 : TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
910 : item->product_id);
911 2 : return;
912 24 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
913 24 : break;
914 : }
915 : }
916 18 : uc->phase++;
917 : }
918 :
919 :
920 : /* *************** Helpers for USE_PHASE_VERIFY ***************** */
921 :
922 : /**
923 : * Check that @a amount is within the limits (if any) the template
924 : * imposes on amounts the client had an influence on. Amounts the
925 : * merchant hard-coded in the template are not subject to these limits.
926 : * Replies with an error if the check fails.
927 : *
928 : * @param[in,out] uc use context
929 : * @param amount amount to check, excluding any tip
930 : * @param detail hint to return to the client on failure
931 : * @return #GNUNET_OK if @a amount is acceptable
932 : */
933 : static enum GNUNET_GenericReturnValue
934 20 : check_amount_limits (struct UseContext *uc,
935 : const struct TALER_Amount *amount,
936 : const char *detail)
937 : {
938 20 : const struct TALER_MERCHANT_TemplateContract *tc = &uc->template_contract;
939 : const struct TALER_Amount *limit;
940 : char *msg;
941 :
942 20 : if (tc->no_min_amount &&
943 16 : tc->no_max_amount)
944 16 : return GNUNET_OK;
945 : /* Parsing the template guarantees both limits to be in the same
946 : currency, so it suffices to check @a amount against either one.
947 : We must check against a limit (and not merely against the
948 : currency of the template) as it is the limits that @a amount is
949 : compared to below, and comparing amounts of different currencies
950 : fails an assertion. */
951 8 : limit = tc->no_min_amount
952 : ? &tc->max_amount
953 4 : : &tc->min_amount;
954 4 : if (GNUNET_YES !=
955 4 : TALER_amount_cmp_currency (amount,
956 : limit))
957 : {
958 0 : GNUNET_break_op (0);
959 0 : use_reply_with_error (uc,
960 : MHD_HTTP_CONFLICT,
961 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
962 0 : limit->currency);
963 0 : return GNUNET_SYSERR;
964 : }
965 8 : if ( (! tc->no_min_amount) &&
966 4 : (0 > TALER_amount_cmp (amount,
967 : &tc->min_amount)) )
968 : {
969 1 : GNUNET_break_op (0);
970 1 : GNUNET_asprintf (&msg,
971 : "%s is below the min_amount of %s",
972 : detail,
973 : TALER_amount2s (&tc->min_amount));
974 : }
975 6 : else if ( (! tc->no_max_amount) &&
976 3 : (0 < TALER_amount_cmp (amount,
977 : &tc->max_amount)) )
978 : {
979 1 : GNUNET_break_op (0);
980 1 : GNUNET_asprintf (&msg,
981 : "%s is above the max_amount of %s",
982 : detail,
983 : TALER_amount2s (&tc->max_amount));
984 : }
985 : else
986 : {
987 2 : return GNUNET_OK;
988 : }
989 2 : use_reply_with_error (
990 : uc,
991 : MHD_HTTP_CONFLICT,
992 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
993 : msg);
994 2 : GNUNET_free (msg);
995 2 : return GNUNET_SYSERR;
996 : }
997 :
998 :
999 : /**
1000 : * Check if the given product ID appears in the array of allowed_products.
1001 : *
1002 : * @param allowed_products JSON array of product IDs allowed by the template, may be NULL
1003 : * @param product_id product ID to check
1004 : * @return true if the product ID is in the list
1005 : */
1006 : static bool
1007 20 : product_id_allowed (const json_t *allowed_products,
1008 : const char *product_id)
1009 : {
1010 : const json_t *entry;
1011 : size_t idx;
1012 :
1013 20 : if (NULL == allowed_products)
1014 0 : return false;
1015 30 : json_array_foreach ((json_t *) allowed_products, idx, entry)
1016 : {
1017 26 : if (! json_is_string (entry))
1018 : {
1019 0 : GNUNET_break (0);
1020 0 : continue;
1021 : }
1022 26 : if (0 == strcmp (json_string_value (entry),
1023 : product_id))
1024 16 : return true;
1025 : }
1026 4 : return false;
1027 : }
1028 :
1029 :
1030 : /**
1031 : * Check if any product category is in the selected_categories list.
1032 : *
1033 : * @param allowed_categories JSON array of categories allowed by the template, may be NULL
1034 : * @param num_categories length of @a categories
1035 : * @param categories list of categories of the selected product
1036 : * @return true if any category of the product is in the list of allowed categories matches
1037 : */
1038 : static bool
1039 4 : category_allowed (const json_t *allowed_categories,
1040 : size_t num_categories,
1041 : const uint64_t categories[num_categories])
1042 4 : {
1043 : const json_t *entry;
1044 : size_t idx;
1045 :
1046 4 : if (NULL == allowed_categories)
1047 2 : return false;
1048 2 : json_array_foreach ((json_t *) allowed_categories,
1049 : idx,
1050 : entry)
1051 : {
1052 : uint64_t selected_id;
1053 :
1054 2 : if (! json_is_integer (entry))
1055 : {
1056 0 : GNUNET_break (0);
1057 0 : continue;
1058 : }
1059 2 : if (0 > json_integer_value (entry))
1060 : {
1061 0 : GNUNET_break (0);
1062 0 : continue;
1063 : }
1064 2 : selected_id = (uint64_t) json_integer_value (entry);
1065 2 : for (size_t i = 0; i < num_categories; i++)
1066 : {
1067 2 : if (categories[i] == selected_id)
1068 2 : return true;
1069 : }
1070 : }
1071 0 : return false;
1072 : }
1073 :
1074 :
1075 : /**
1076 : * Verify request data for inventory templates.
1077 : * Checks that the selected products are allowed
1078 : * for this template.
1079 : *
1080 : * @param[in,out] uc use context
1081 : * @return #GNUNET_OK on success
1082 : */
1083 : static enum GNUNET_GenericReturnValue
1084 18 : verify_using_templates_inventory (struct UseContext *uc)
1085 : {
1086 18 : if (uc->template_contract.details.inventory.choose_one &&
1087 10 : (1 != uc->parse_request.inventory.items_len))
1088 : {
1089 4 : GNUNET_break_op (0);
1090 4 : use_reply_with_error (uc,
1091 : MHD_HTTP_CONFLICT,
1092 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1093 : "inventory_selection");
1094 4 : return GNUNET_SYSERR;
1095 : }
1096 14 : if (uc->template_contract.details.inventory.selected_all)
1097 0 : return GNUNET_OK;
1098 14 : for (unsigned int i = 0;
1099 34 : i < uc->parse_request.inventory.items_len;
1100 20 : i++)
1101 : {
1102 20 : struct InventoryTemplateItemContext *item =
1103 20 : &uc->parse_request.inventory.items[i];
1104 20 : const char *eparam = NULL;
1105 :
1106 20 : if (GNUNET_OK !=
1107 20 : TALER_MERCHANT_vk_process_quantity_inputs (
1108 : TALER_MERCHANT_VK_QUANTITY,
1109 20 : item->pd.allow_fractional_quantity,
1110 : true,
1111 : 0,
1112 : false,
1113 : item->unit_quantity,
1114 : &item->quantity_value,
1115 : &item->quantity_frac,
1116 : &eparam))
1117 : {
1118 0 : GNUNET_break_op (0);
1119 0 : use_reply_with_error (uc,
1120 : MHD_HTTP_BAD_REQUEST,
1121 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1122 : eparam);
1123 0 : return GNUNET_SYSERR;
1124 : }
1125 :
1126 20 : if (0 == item->pd.price_array_length)
1127 : {
1128 0 : GNUNET_break (0);
1129 0 : use_reply_with_error (uc,
1130 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1131 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1132 : "price_array");
1133 0 : return GNUNET_SYSERR;
1134 : }
1135 :
1136 : /* The line-total computation multiplies the unit price by the
1137 : integer quantity using TALER_amount_multiply(), whose factor
1138 : argument is only a uint32_t. A quantity that does not fit into
1139 : 32 bits would be silently truncated by the (uint32_t) cast in
1140 : compute_line_total() (e.g. quantity == 2^32 truncates to 0),
1141 : producing a wrong -- and attacker-controllable, much too low --
1142 : price. Reject such quantities rather than truncate them. */
1143 20 : if (item->quantity_value > UINT32_MAX)
1144 : {
1145 0 : GNUNET_break_op (0);
1146 0 : use_reply_with_error (uc,
1147 : MHD_HTTP_BAD_REQUEST,
1148 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1149 : "unit_quantity");
1150 0 : return GNUNET_SYSERR;
1151 : }
1152 : }
1153 :
1154 14 : for (unsigned int i = 0;
1155 32 : i < uc->parse_request.inventory.items_len;
1156 18 : i++)
1157 : {
1158 20 : struct InventoryTemplateItemContext *item =
1159 20 : &uc->parse_request.inventory.items[i];
1160 :
1161 20 : if (product_id_allowed (uc->template_contract.details.inventory.
1162 : selected_products,
1163 : item->product_id))
1164 16 : continue;
1165 4 : if (category_allowed (
1166 : uc->template_contract.details.inventory.selected_categories,
1167 : item->num_categories,
1168 4 : item->categories))
1169 2 : continue;
1170 2 : GNUNET_break_op (0);
1171 2 : use_reply_with_error (
1172 : uc,
1173 : MHD_HTTP_CONFLICT,
1174 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT,
1175 : item->product_id);
1176 2 : return GNUNET_SYSERR;
1177 : }
1178 12 : return GNUNET_OK;
1179 : }
1180 :
1181 :
1182 : /**
1183 : * Verify request data for fixed-order templates.
1184 : * As here we cannot compute the total amount, either
1185 : * the template or the client request must provide it.
1186 : *
1187 : * @param[in,out] uc use context
1188 : * @return #GNUNET_OK on success
1189 : */
1190 : static enum GNUNET_GenericReturnValue
1191 14 : verify_using_templates_fixed (
1192 : struct UseContext *uc)
1193 : {
1194 14 : if ( (! uc->parse_request.no_amount) &&
1195 8 : (! uc->template_contract.no_amount) )
1196 : {
1197 4 : GNUNET_break_op (0);
1198 4 : use_reply_with_error (uc,
1199 : MHD_HTTP_CONFLICT,
1200 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
1201 : NULL);
1202 4 : return GNUNET_SYSERR;
1203 : }
1204 10 : if (uc->parse_request.no_amount &&
1205 6 : uc->template_contract.no_amount)
1206 : {
1207 2 : GNUNET_break_op (0);
1208 2 : use_reply_with_error (uc,
1209 : MHD_HTTP_CONFLICT,
1210 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
1211 : NULL);
1212 2 : return GNUNET_SYSERR;
1213 : }
1214 8 : return GNUNET_OK;
1215 : }
1216 :
1217 :
1218 : /**
1219 : * Turn a plain @e amount given by the client into an entry in the
1220 : * @e choice_amounts array. This shorthand is what the generic
1221 : * template flow (``editable_defaults`` and ``taler://pay-template``
1222 : * URIs) can express, and thus only works if the template has exactly
1223 : * one choice with an editable amount.
1224 : *
1225 : * @param[in,out] uc use context
1226 : * @return #GNUNET_OK on success
1227 : */
1228 : static enum GNUNET_GenericReturnValue
1229 4 : resolve_paivana_amount_shorthand (
1230 : struct UseContext *uc)
1231 : {
1232 4 : const struct TALER_MERCHANT_TemplateContractPaivana *tcp
1233 : = &uc->template_contract.details.paivana;
1234 4 : struct PaivanaChoiceAmount ca = {
1235 : .amount = uc->parse_request.amount
1236 : };
1237 4 : unsigned int editable = 0;
1238 :
1239 8 : for (unsigned int i = 0; i < tcp->choices_len; i++)
1240 : {
1241 4 : if (! tcp->choices[i].editable_amount)
1242 1 : continue;
1243 3 : ca.choice_index = i;
1244 3 : editable++;
1245 : }
1246 4 : if (0 == editable)
1247 : {
1248 1 : GNUNET_break_op (0);
1249 1 : use_reply_with_error (
1250 : uc,
1251 : MHD_HTTP_CONFLICT,
1252 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
1253 : "template does not allow the amount to be edited");
1254 1 : return GNUNET_SYSERR;
1255 : }
1256 3 : if (1 != editable)
1257 : {
1258 0 : GNUNET_break_op (0);
1259 0 : use_reply_with_error (
1260 : uc,
1261 : MHD_HTTP_CONFLICT,
1262 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
1263 : "template has more than one editable choice, use choice_amounts");
1264 0 : return GNUNET_SYSERR;
1265 : }
1266 3 : GNUNET_array_append (uc->parse_request.paivana.choice_amounts,
1267 : uc->parse_request.paivana.choice_amounts_len,
1268 : ca);
1269 3 : return GNUNET_OK;
1270 : }
1271 :
1272 :
1273 : /**
1274 : * Verify request data for paivana templates.
1275 : *
1276 : * @param[in,out] uc use context
1277 : * @return #GNUNET_OK on success
1278 : */
1279 : static enum GNUNET_GenericReturnValue
1280 7 : verify_using_templates_paivana (
1281 : struct UseContext *uc)
1282 : {
1283 7 : const struct TALER_MERCHANT_TemplateContractPaivana *tcp
1284 : = &uc->template_contract.details.paivana;
1285 :
1286 11 : if ( (! uc->parse_request.no_amount) &&
1287 : (GNUNET_OK !=
1288 4 : resolve_paivana_amount_shorthand (uc)) )
1289 1 : return GNUNET_SYSERR;
1290 6 : for (unsigned int i = 0;
1291 8 : i < uc->parse_request.paivana.choice_amounts_len;
1292 2 : i++)
1293 : {
1294 5 : const struct PaivanaChoiceAmount *ca
1295 5 : = &uc->parse_request.paivana.choice_amounts[i];
1296 : const struct TALER_MERCHANT_OrderChoice *choice;
1297 :
1298 5 : if (ca->choice_index >= tcp->choices_len)
1299 : {
1300 1 : GNUNET_break_op (0);
1301 1 : use_reply_with_error (uc,
1302 : MHD_HTTP_BAD_REQUEST,
1303 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1304 : "choice_amounts::choice_index out of range");
1305 1 : return GNUNET_SYSERR;
1306 : }
1307 4 : choice = &tcp->choices[ca->choice_index];
1308 4 : if (! choice->editable_amount)
1309 : {
1310 0 : GNUNET_break_op (0);
1311 0 : use_reply_with_error (
1312 : uc,
1313 : MHD_HTTP_CONFLICT,
1314 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
1315 : "selected choice has a fixed amount");
1316 0 : return GNUNET_SYSERR;
1317 : }
1318 : /* The currency is baked into the choice (say via 'max_fee'),
1319 : so the client may only change the value, not the currency. */
1320 4 : if (GNUNET_YES !=
1321 4 : TALER_amount_cmp_currency (&ca->amount,
1322 : &choice->amount))
1323 : {
1324 0 : GNUNET_break_op (0);
1325 0 : use_reply_with_error (uc,
1326 : MHD_HTTP_CONFLICT,
1327 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
1328 0 : choice->amount.currency);
1329 0 : return GNUNET_SYSERR;
1330 : }
1331 4 : if (GNUNET_OK !=
1332 4 : check_amount_limits (uc,
1333 : &ca->amount,
1334 : "amount of the selected choice"))
1335 2 : return GNUNET_SYSERR;
1336 : }
1337 3 : if (NULL != uc->template_contract.details.paivana.website_regex)
1338 : {
1339 : regex_t ex;
1340 0 : bool allowed = false;
1341 :
1342 0 : if (0 != regcomp (&ex,
1343 : uc->template_contract.details.paivana.website_regex,
1344 : REG_NOSUB | REG_EXTENDED))
1345 : {
1346 0 : GNUNET_break_op (0);
1347 0 : return GNUNET_SYSERR;
1348 : }
1349 0 : if (0 ==
1350 0 : regexec (&ex,
1351 : uc->parse_request.paivana.website,
1352 : 0, NULL,
1353 : 0))
1354 : {
1355 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1356 : "Website `%s' allowed by template\n",
1357 : uc->parse_request.paivana.website);
1358 0 : allowed = true;
1359 : }
1360 0 : regfree (&ex);
1361 0 : if (! allowed)
1362 : {
1363 0 : GNUNET_break_op (0);
1364 0 : use_reply_with_error (uc,
1365 : MHD_HTTP_BAD_REQUEST,
1366 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1367 : "website_regex");
1368 0 : return GNUNET_SYSERR;
1369 : }
1370 : }
1371 3 : return GNUNET_OK;
1372 : }
1373 :
1374 :
1375 : /**
1376 : * Verify that the client request is structurally acceptable for the specified
1377 : * template. Does NOT check the total amount being reasonable.
1378 : *
1379 : * @param[in,out] uc use context
1380 : */
1381 : static void
1382 41 : handle_phase_verify (
1383 : struct UseContext *uc)
1384 : {
1385 41 : enum GNUNET_GenericReturnValue res = GNUNET_SYSERR;
1386 :
1387 41 : if ( (NULL != uc->parse_request.summary) &&
1388 10 : (NULL != uc->template_contract.summary) )
1389 : {
1390 0 : GNUNET_break_op (0);
1391 0 : use_reply_with_error (uc,
1392 : MHD_HTTP_CONFLICT,
1393 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
1394 : NULL);
1395 0 : return;
1396 : }
1397 41 : if ( (NULL == uc->parse_request.summary) &&
1398 31 : (NULL == uc->template_contract.summary) )
1399 : {
1400 2 : GNUNET_break_op (0);
1401 2 : use_reply_with_error (uc,
1402 : MHD_HTTP_CONFLICT,
1403 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
1404 : NULL);
1405 2 : return;
1406 : }
1407 39 : if ( (! uc->parse_request.no_amount) &&
1408 30 : (NULL != uc->template_contract.currency) &&
1409 3 : (0 != strcasecmp (uc->template_contract.currency,
1410 3 : uc->parse_request.amount.currency)) )
1411 : {
1412 0 : GNUNET_break_op (0);
1413 0 : use_reply_with_error (uc,
1414 : MHD_HTTP_CONFLICT,
1415 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
1416 : uc->template_contract.currency);
1417 0 : return;
1418 : }
1419 39 : switch (uc->template_type)
1420 : {
1421 14 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
1422 14 : res = verify_using_templates_fixed (uc);
1423 14 : break;
1424 7 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
1425 7 : res = verify_using_templates_paivana (uc);
1426 7 : break;
1427 18 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
1428 18 : res = verify_using_templates_inventory (uc);
1429 18 : break;
1430 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
1431 0 : GNUNET_assert (0);
1432 : }
1433 39 : if (GNUNET_OK == res)
1434 23 : uc->phase++;
1435 : }
1436 :
1437 :
1438 : /* ***************** USE_PHASE_COMPUTE_PRICE **************** */
1439 :
1440 :
1441 : /**
1442 : * Find the amount the client picked for the choice at @a choice_index
1443 : * of a paivana template.
1444 : *
1445 : * @param uc use context
1446 : * @param choice_index index into the choices of the template contract
1447 : * @return NULL if the client did not pick an amount for this choice,
1448 : * in which case the amount from the template applies
1449 : */
1450 : static const struct TALER_Amount *
1451 3 : find_paivana_choice_amount (const struct UseContext *uc,
1452 : unsigned int choice_index)
1453 : {
1454 3 : for (unsigned int i = 0;
1455 3 : i < uc->parse_request.paivana.choice_amounts_len;
1456 0 : i++)
1457 : {
1458 2 : const struct PaivanaChoiceAmount *ca
1459 2 : = &uc->parse_request.paivana.choice_amounts[i];
1460 :
1461 2 : if (choice_index == ca->choice_index)
1462 2 : return &ca->amount;
1463 : }
1464 1 : return NULL;
1465 : }
1466 :
1467 :
1468 : /**
1469 : * Compute the line total for a product based on quantity.
1470 : *
1471 : * @param unit_price price per unit
1472 : * @param quantity integer quantity
1473 : * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1)
1474 : * @param[out] line_total resulting line total
1475 : * @return #GNUNET_OK on success
1476 : */
1477 : static enum GNUNET_GenericReturnValue
1478 18 : compute_line_total (const struct TALER_Amount *unit_price,
1479 : uint64_t quantity,
1480 : uint32_t quantity_frac,
1481 : struct TALER_Amount *line_total)
1482 : {
1483 : struct TALER_Amount tmp;
1484 :
1485 18 : GNUNET_assert (GNUNET_OK ==
1486 : TALER_amount_set_zero (unit_price->currency,
1487 : line_total));
1488 34 : if ( (0 != quantity) &&
1489 : (0 >
1490 16 : TALER_amount_multiply (line_total,
1491 : unit_price,
1492 : (uint32_t) quantity)) )
1493 : {
1494 0 : GNUNET_break (0);
1495 0 : return GNUNET_SYSERR;
1496 : }
1497 18 : if (0 == quantity_frac)
1498 16 : return GNUNET_OK;
1499 2 : if (0 >
1500 2 : TALER_amount_multiply (&tmp,
1501 : unit_price,
1502 : quantity_frac))
1503 : {
1504 0 : GNUNET_break (0);
1505 0 : return GNUNET_SYSERR;
1506 : }
1507 2 : TALER_amount_divide (&tmp,
1508 : &tmp,
1509 : TALER_MERCHANT_UNIT_FRAC_BASE);
1510 2 : if (0 >
1511 2 : TALER_amount_add (line_total,
1512 : line_total,
1513 : &tmp))
1514 : {
1515 0 : GNUNET_break (0);
1516 0 : return GNUNET_SYSERR;
1517 : }
1518 2 : return GNUNET_OK;
1519 : }
1520 :
1521 :
1522 : /**
1523 : * Find the price of the given @a item in the specified
1524 : * @a currency.
1525 : *
1526 : * @param currency currency to search price in
1527 : * @param item item to check prices of
1528 : * @return NULL if a suitable price was not found
1529 : */
1530 : static const struct TALER_Amount *
1531 18 : find_item_price_in_currency (
1532 : const char *currency,
1533 : const struct InventoryTemplateItemContext *item)
1534 : {
1535 18 : for (size_t j = 0; j < item->pd.price_array_length; j++)
1536 : {
1537 18 : if (0 == strcasecmp (item->pd.price_array[j].currency,
1538 : currency))
1539 18 : return &item->pd.price_array[j];
1540 : }
1541 0 : return NULL;
1542 : }
1543 :
1544 :
1545 : /**
1546 : * Compute totals for all currencies shared across selected products.
1547 : *
1548 : * @param[in,out] uc use context
1549 : * @return #GNUNET_OK on success (including no price due to no items)
1550 : * #GNUNET_NO if we could not find a price in any accepted currency
1551 : * for all selected products
1552 : * #GNUNET_SYSERR on arithmetic issues (internal error)
1553 : */
1554 : static enum GNUNET_GenericReturnValue
1555 0 : compute_totals_per_currency (struct UseContext *uc)
1556 : {
1557 0 : const struct InventoryTemplateItemContext *items
1558 : = uc->parse_request.inventory.items;
1559 0 : unsigned int items_len = uc->parse_request.inventory.items_len;
1560 :
1561 0 : if (0 == items_len)
1562 0 : return GNUNET_NO;
1563 0 : for (size_t i = 0; i < items[0].pd.price_array_length; i++)
1564 : {
1565 0 : const struct TALER_Amount *price
1566 0 : = &items[0].pd.price_array[i];
1567 : struct TALER_Amount zero;
1568 :
1569 0 : if (! TMH_test_exchange_configured_for_currency (price->currency))
1570 0 : continue;
1571 0 : GNUNET_assert (GNUNET_OK ==
1572 : TALER_amount_set_zero (price->currency,
1573 : &zero));
1574 0 : GNUNET_array_append (uc->compute_price.totals,
1575 : uc->compute_price.totals_len,
1576 : zero);
1577 : }
1578 0 : if (0 == uc->compute_price.totals_len)
1579 : {
1580 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1581 : "No currency supported by our configuration in which we have prices for first selected product!\n");
1582 0 : return GNUNET_NO;
1583 : }
1584 : /* Loop through items, ensure each currency exists and sum totals. */
1585 0 : for (unsigned int i = 0; i < items_len; i++)
1586 : {
1587 0 : const struct InventoryTemplateItemContext *item = &items[i];
1588 0 : unsigned int c = 0;
1589 :
1590 0 : while (c < uc->compute_price.totals_len)
1591 : {
1592 0 : struct TALER_Amount *total = &uc->compute_price.totals[c];
1593 : const struct TALER_Amount *unit_price;
1594 : struct TALER_Amount line_total;
1595 :
1596 0 : unit_price = find_item_price_in_currency (total->currency,
1597 : item);
1598 0 : if (NULL == unit_price)
1599 : {
1600 : /* Drop the currency: we have no price in one of
1601 : the selected products */
1602 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1603 : "Product `%s' has no price in %s: dropping currency\n",
1604 : item->product_id,
1605 : total->currency);
1606 0 : *total = uc->compute_price.totals[--uc->compute_price.totals_len];
1607 0 : continue;
1608 : }
1609 0 : if (GNUNET_OK !=
1610 0 : compute_line_total (unit_price,
1611 0 : item->quantity_value,
1612 0 : item->quantity_frac,
1613 : &line_total))
1614 : {
1615 0 : GNUNET_break (0);
1616 0 : return GNUNET_SYSERR;
1617 : }
1618 0 : if (0 >
1619 0 : TALER_amount_add (total,
1620 : total,
1621 : &line_total))
1622 : {
1623 0 : GNUNET_break (0);
1624 0 : return GNUNET_SYSERR;
1625 : }
1626 0 : c++;
1627 : }
1628 : }
1629 0 : if (0 == uc->compute_price.totals_len)
1630 : {
1631 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1632 : "No currency available in which we have prices for all selected products!\n");
1633 0 : GNUNET_free (uc->compute_price.totals);
1634 : }
1635 0 : return (0 == uc->compute_price.totals_len)
1636 : ? GNUNET_NO
1637 0 : : GNUNET_OK;
1638 : }
1639 :
1640 :
1641 : /**
1642 : * Compute total for only the given @a currency.
1643 : *
1644 : * @param items_len length of @a items
1645 : * @param items inventory items
1646 : * @param currency currency to total
1647 : * @param[out] total computed total
1648 : * @return #GNUNET_OK on success
1649 : * #GNUNET_NO if we could not find a price in any accepted currency
1650 : * for all selected products
1651 : * #GNUNET_SYSERR on arithmetic issues (internal error)
1652 : */
1653 : static enum GNUNET_GenericReturnValue
1654 12 : compute_inventory_total (unsigned int items_len,
1655 : const struct InventoryTemplateItemContext *items,
1656 : const char *currency,
1657 : struct TALER_Amount *total)
1658 : {
1659 12 : GNUNET_assert (NULL != currency);
1660 12 : GNUNET_assert (GNUNET_OK ==
1661 : TALER_amount_set_zero (currency,
1662 : total));
1663 30 : for (unsigned int i = 0; i < items_len; i++)
1664 : {
1665 18 : const struct InventoryTemplateItemContext *item = &items[i];
1666 : const struct TALER_Amount *unit_price;
1667 : struct TALER_Amount line_total;
1668 :
1669 18 : unit_price = find_item_price_in_currency (currency,
1670 : item);
1671 18 : if (NULL == unit_price)
1672 : {
1673 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1674 : "compute_inventory_total: no price in %s for product `%s'\n",
1675 : currency,
1676 : item->product_id);
1677 0 : return GNUNET_NO;
1678 : }
1679 18 : if (GNUNET_OK !=
1680 18 : compute_line_total (unit_price,
1681 18 : item->quantity_value,
1682 18 : item->quantity_frac,
1683 : &line_total))
1684 : {
1685 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1686 : "compute_inventory_total: line total failed for %s in %s\n",
1687 : item->product_id,
1688 : currency);
1689 0 : return GNUNET_SYSERR;
1690 : }
1691 18 : if (0 >
1692 18 : TALER_amount_add (total,
1693 : total,
1694 : &line_total))
1695 : {
1696 0 : GNUNET_break (0);
1697 0 : return GNUNET_SYSERR;
1698 : }
1699 : }
1700 12 : return GNUNET_OK;
1701 : }
1702 :
1703 :
1704 : /**
1705 : * Compute total price.
1706 : *
1707 : * @param[in,out] uc use context
1708 : */
1709 : static void
1710 23 : handle_phase_compute_price (struct UseContext *uc)
1711 : {
1712 : const char *primary_currency;
1713 : enum GNUNET_GenericReturnValue ret;
1714 :
1715 23 : switch (uc->template_type)
1716 : {
1717 8 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
1718 : uc->compute_price.totals
1719 8 : = GNUNET_new (struct TALER_Amount);
1720 : uc->compute_price.totals_len
1721 8 : = 1;
1722 8 : if (uc->parse_request.no_amount)
1723 : {
1724 4 : GNUNET_assert (! uc->template_contract.no_amount);
1725 4 : *uc->compute_price.totals
1726 4 : = uc->template_contract.amount;
1727 : }
1728 : else
1729 : {
1730 4 : GNUNET_assert (uc->template_contract.no_amount);
1731 4 : *uc->compute_price.totals
1732 4 : = uc->parse_request.amount;
1733 4 : if (! uc->parse_request.no_tip)
1734 : {
1735 : /* Per the API specification, the client's 'amount' is "the
1736 : amount to be paid, including tip", while the total we compute
1737 : in this phase excludes the tip: #handle_phase_check_tip()
1738 : adds it back on top. Without removing it here, the total
1739 : would end up being 'amount' + 'tip' and could thus never
1740 : match the 'amount' that #handle_phase_check_total() compares
1741 : it against. */
1742 0 : if (GNUNET_YES !=
1743 0 : TALER_amount_cmp_currency (&uc->parse_request.tip,
1744 0 : uc->compute_price.totals))
1745 : {
1746 0 : GNUNET_break_op (0);
1747 0 : use_reply_with_error (uc,
1748 : MHD_HTTP_CONFLICT,
1749 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
1750 0 : uc->parse_request.tip.currency);
1751 0 : return;
1752 : }
1753 0 : if (0 >
1754 0 : TALER_amount_subtract (uc->compute_price.totals,
1755 0 : uc->compute_price.totals,
1756 0 : &uc->parse_request.tip))
1757 : {
1758 0 : GNUNET_break_op (0);
1759 0 : use_reply_with_error (
1760 : uc,
1761 : MHD_HTTP_CONFLICT,
1762 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
1763 : "tip exceeds amount");
1764 0 : return;
1765 : }
1766 : }
1767 : /* Only an amount the client chose is subject to the limits. */
1768 4 : if (GNUNET_OK !=
1769 4 : check_amount_limits (uc,
1770 4 : uc->compute_price.totals,
1771 : "amount"))
1772 0 : return;
1773 : }
1774 8 : uc->phase++;
1775 8 : return;
1776 12 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
1777 : /* handled below */
1778 12 : break;
1779 3 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
1780 : {
1781 3 : const struct TALER_MERCHANT_TemplateContractPaivana *tcp
1782 : = &uc->template_contract.details.paivana;
1783 : json_t *choices;
1784 :
1785 3 : choices = json_array ();
1786 3 : GNUNET_assert (NULL != choices);
1787 6 : for (size_t i = 0; i < tcp->choices_len; i++)
1788 : {
1789 : /* Make deep copy, we're going to MODIFY it! */
1790 3 : struct TALER_MERCHANT_OrderChoice choice
1791 3 : = tcp->choices[i];
1792 : const struct TALER_Amount *ca;
1793 :
1794 3 : ca = find_paivana_choice_amount (uc,
1795 : i);
1796 3 : if (NULL != ca)
1797 2 : choice.amount = *ca;
1798 3 : choice.no_tip = uc->parse_request.no_tip;
1799 3 : if (! uc->parse_request.no_tip)
1800 : {
1801 2 : if (GNUNET_YES !=
1802 2 : TALER_amount_cmp_currency (&choice.amount,
1803 2 : &uc->parse_request.tip))
1804 0 : continue; /* tip does not match choice currency */
1805 2 : choice.tip = uc->parse_request.tip;
1806 2 : if (0 >
1807 2 : TALER_amount_add (&choice.amount,
1808 : &choice.amount,
1809 2 : &uc->parse_request.tip))
1810 : {
1811 0 : GNUNET_break (0);
1812 0 : use_reply_with_error (uc,
1813 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1814 : TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
1815 : "tip");
1816 0 : return;
1817 : }
1818 : }
1819 3 : GNUNET_assert (0 ==
1820 : json_array_append_new (
1821 : choices,
1822 : TALER_MERCHANT_json_from_order_choice (&choice)));
1823 : }
1824 3 : if (0 == json_array_size (choices))
1825 : {
1826 0 : GNUNET_break_op (0);
1827 0 : json_decref (choices);
1828 0 : use_reply_with_error (uc,
1829 : MHD_HTTP_CONFLICT,
1830 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
1831 : "tip");
1832 0 : return;
1833 : }
1834 3 : uc->compute_price.choices = choices;
1835 : }
1836 : /* Note: we already did the tip and pricing
1837 : fully here, so we skip these phases. */
1838 3 : uc->phase = USE_PHASE_CREATE_ORDER;
1839 3 : return;
1840 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
1841 0 : GNUNET_assert (0);
1842 : }
1843 12 : primary_currency = uc->template_contract.currency;
1844 12 : if (! uc->parse_request.no_tip)
1845 4 : primary_currency = uc->parse_request.tip.currency;
1846 : /* An 'amount' given by the client takes precedence over the currency
1847 : of the 'tip': the total we compute here is later compared against
1848 : that 'amount' in #handle_phase_check_total(), which requires both
1849 : to be in the same currency. If the tip then uses a different
1850 : currency, #handle_phase_check_tip() rejects the request with a
1851 : proper error instead. */
1852 12 : if (! uc->parse_request.no_amount)
1853 12 : primary_currency = uc->parse_request.amount.currency;
1854 12 : if (NULL == primary_currency)
1855 : {
1856 0 : ret = compute_totals_per_currency (uc);
1857 : }
1858 : else
1859 : {
1860 : uc->compute_price.totals
1861 12 : = GNUNET_new (struct TALER_Amount);
1862 : uc->compute_price.totals_len
1863 12 : = 1;
1864 12 : ret = compute_inventory_total (
1865 : uc->parse_request.inventory.items_len,
1866 12 : uc->parse_request.inventory.items,
1867 : primary_currency,
1868 : uc->compute_price.totals);
1869 : }
1870 12 : if (GNUNET_SYSERR == ret)
1871 : {
1872 0 : use_reply_with_error (
1873 : uc,
1874 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1875 : TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
1876 : "calculation of currency totals failed");
1877 0 : return;
1878 : }
1879 12 : if (GNUNET_NO == ret)
1880 : {
1881 0 : use_reply_with_error (uc,
1882 : MHD_HTTP_CONFLICT,
1883 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
1884 : NULL);
1885 0 : return;
1886 : }
1887 : /* The client picked the products and quantities, so the
1888 : resulting total is subject to the limits. */
1889 12 : for (unsigned int i = 0;
1890 24 : i < uc->compute_price.totals_len;
1891 12 : i++)
1892 : {
1893 12 : if (GNUNET_OK !=
1894 12 : check_amount_limits (uc,
1895 12 : &uc->compute_price.totals[i],
1896 : "total of the selected products"))
1897 0 : return;
1898 : }
1899 :
1900 12 : uc->phase++;
1901 : }
1902 :
1903 :
1904 : /* ***************** USE_PHASE_CHECK_TIP **************** */
1905 :
1906 :
1907 : /**
1908 : * Check that tip specified is reasonable and add to total.
1909 : *
1910 : * @param[in,out] uc use context
1911 : */
1912 : static void
1913 20 : handle_phase_check_tip (struct UseContext *uc)
1914 : {
1915 : struct TALER_Amount *total_amount;
1916 :
1917 20 : if (uc->parse_request.no_tip)
1918 : {
1919 16 : uc->phase++;
1920 16 : return;
1921 : }
1922 4 : if (0 == uc->compute_price.totals_len)
1923 : {
1924 0 : if (! TMH_test_exchange_configured_for_currency (
1925 0 : uc->parse_request.tip.currency))
1926 : {
1927 0 : GNUNET_break_op (0);
1928 0 : use_reply_with_error (uc,
1929 : MHD_HTTP_CONFLICT,
1930 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
1931 : "Tip currency is not supported by backend");
1932 0 : return;
1933 : }
1934 : uc->compute_price.totals
1935 0 : = GNUNET_new (struct TALER_Amount);
1936 : uc->compute_price.totals_len
1937 0 : = 1;
1938 0 : *uc->compute_price.totals
1939 0 : = uc->parse_request.tip;
1940 0 : uc->phase++;
1941 0 : return;
1942 : }
1943 4 : GNUNET_assert (1 == uc->compute_price.totals_len);
1944 4 : total_amount = &uc->compute_price.totals[0];
1945 4 : if (GNUNET_YES !=
1946 4 : TALER_amount_cmp_currency (&uc->parse_request.tip,
1947 : total_amount))
1948 : {
1949 0 : GNUNET_break_op (0);
1950 0 : use_reply_with_error (uc,
1951 : MHD_HTTP_CONFLICT,
1952 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
1953 0 : uc->parse_request.tip.currency);
1954 0 : return;
1955 : }
1956 4 : if (0 >
1957 4 : TALER_amount_add (total_amount,
1958 : total_amount,
1959 4 : &uc->parse_request.tip))
1960 : {
1961 0 : GNUNET_break (0);
1962 0 : use_reply_with_error (uc,
1963 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1964 : TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
1965 : "tip");
1966 0 : return;
1967 : }
1968 4 : uc->phase++;
1969 : }
1970 :
1971 :
1972 : /* ***************** USE_PHASE_CHECK_TOTAL **************** */
1973 :
1974 : /**
1975 : * Check that if the client specified a total,
1976 : * it matches our own calculation.
1977 : *
1978 : * @param[in,out] uc use context
1979 : */
1980 : static void
1981 20 : handle_phase_check_total (struct UseContext *uc)
1982 : {
1983 20 : GNUNET_assert (1 <= uc->compute_price.totals_len);
1984 20 : if (! uc->parse_request.no_amount)
1985 : {
1986 16 : GNUNET_assert (1 == uc->compute_price.totals_len);
1987 16 : if (GNUNET_YES !=
1988 16 : TALER_amount_cmp_currency (&uc->parse_request.amount,
1989 16 : &uc->compute_price.totals[0]))
1990 : {
1991 : /* Must not be an assertion: the currency of the total we computed
1992 : is influenced by the client (via 'tip' and the selected
1993 : products), so a mismatch here is remotely triggerable. */
1994 0 : GNUNET_break_op (0);
1995 0 : use_reply_with_error (uc,
1996 : MHD_HTTP_CONFLICT,
1997 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
1998 0 : uc->compute_price.totals[0].currency);
1999 0 : return;
2000 : }
2001 16 : if (0 !=
2002 16 : TALER_amount_cmp (&uc->parse_request.amount,
2003 16 : &uc->compute_price.totals[0]))
2004 : {
2005 4 : GNUNET_break_op (0);
2006 4 : use_reply_with_error (uc,
2007 : MHD_HTTP_CONFLICT,
2008 : TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
2009 4 : TALER_amount2s (&uc->compute_price.totals[0]));
2010 4 : return;
2011 : }
2012 : }
2013 16 : uc->phase++;
2014 : }
2015 :
2016 :
2017 : /* ***************** USE_PHASE_CREATE_ORDER **************** */
2018 :
2019 :
2020 : /**
2021 : * Create order request for inventory templates.
2022 : *
2023 : * @param[in,out] uc use context
2024 : */
2025 : static void
2026 8 : create_using_templates_inventory (struct UseContext *uc)
2027 : {
2028 : json_t *inventory_products;
2029 : json_t *choices;
2030 :
2031 8 : inventory_products = json_array ();
2032 8 : GNUNET_assert (NULL != inventory_products);
2033 8 : for (unsigned int i = 0;
2034 20 : i < uc->parse_request.inventory.items_len;
2035 12 : i++)
2036 : {
2037 12 : const struct InventoryTemplateItemContext *item =
2038 12 : &uc->parse_request.inventory.items[i];
2039 :
2040 12 : GNUNET_assert (0 ==
2041 : json_array_append_new (
2042 : inventory_products,
2043 : GNUNET_JSON_PACK (
2044 : GNUNET_JSON_pack_string ("product_id",
2045 : item->product_id),
2046 : GNUNET_JSON_pack_string ("unit_quantity",
2047 : item->unit_quantity))));
2048 : }
2049 8 : choices = json_array ();
2050 8 : GNUNET_assert (NULL != choices);
2051 8 : for (unsigned int i = 0;
2052 16 : i < uc->compute_price.totals_len;
2053 8 : i++)
2054 : {
2055 8 : GNUNET_assert (0 ==
2056 : json_array_append_new (
2057 : choices,
2058 : GNUNET_JSON_PACK (
2059 : TALER_JSON_pack_amount ("amount",
2060 : &uc->compute_price.totals[i]),
2061 : GNUNET_JSON_pack_allow_null (
2062 : TALER_JSON_pack_amount ("tip",
2063 : uc->parse_request.no_tip
2064 : ? NULL
2065 : : &uc->parse_request.tip))
2066 : )));
2067 : }
2068 :
2069 : uc->ihc.request_body
2070 8 : = GNUNET_JSON_PACK (
2071 : GNUNET_JSON_pack_allow_null (
2072 : GNUNET_JSON_pack_string ("otp_id",
2073 : uc->lookup_template.etp.otp_id)),
2074 : GNUNET_JSON_pack_array_steal ("inventory_products",
2075 : inventory_products),
2076 : GNUNET_JSON_pack_object_steal (
2077 : "order",
2078 : GNUNET_JSON_PACK (
2079 : GNUNET_JSON_pack_uint64 ("version",
2080 : 1),
2081 : GNUNET_JSON_pack_array_steal ("choices",
2082 : choices),
2083 : GNUNET_JSON_pack_string ("summary",
2084 : NULL == uc->parse_request.summary
2085 : ? uc->template_contract.summary
2086 : : uc->parse_request.summary))));
2087 8 : if (! GNUNET_TIME_relative_is_forever (
2088 : uc->template_contract.max_pickup_duration))
2089 : {
2090 0 : GNUNET_assert (
2091 : 0 ==
2092 : json_object_set_new (
2093 : uc->ihc.request_body,
2094 : "max_pickup_time",
2095 : GNUNET_JSON_from_timestamp (
2096 : GNUNET_TIME_absolute_to_timestamp (
2097 : GNUNET_TIME_relative_to_absolute (
2098 : uc->template_contract.max_pickup_duration)))));
2099 : }
2100 8 : }
2101 :
2102 :
2103 : /**
2104 : * Create order request for fixed-order templates.
2105 : *
2106 : * @param[in,out] uc use context
2107 : */
2108 : static void
2109 8 : create_using_templates_fixed (struct UseContext *uc)
2110 : {
2111 : uc->ihc.request_body
2112 8 : = GNUNET_JSON_PACK (
2113 : GNUNET_JSON_pack_allow_null (
2114 : GNUNET_JSON_pack_string ("otp_id",
2115 : uc->lookup_template.etp.otp_id)),
2116 : GNUNET_JSON_pack_object_steal (
2117 : "order",
2118 : GNUNET_JSON_PACK (
2119 : TALER_JSON_pack_amount (
2120 : "amount",
2121 : &uc->compute_price.totals[0]),
2122 : GNUNET_JSON_pack_allow_null (
2123 : TALER_JSON_pack_amount ("tip",
2124 : uc->parse_request.no_tip
2125 : ? NULL
2126 : : &uc->parse_request.tip)),
2127 : GNUNET_JSON_pack_string (
2128 : "summary",
2129 : NULL == uc->parse_request.summary
2130 : ? uc->template_contract.summary
2131 : : uc->parse_request.summary))));
2132 8 : }
2133 :
2134 :
2135 : /**
2136 : * Create order request for paivana templates.
2137 : *
2138 : * @param[in,out] uc use context
2139 : */
2140 : static void
2141 3 : create_using_templates_paivana (struct UseContext *uc)
2142 : {
2143 : uc->ihc.request_body
2144 3 : = GNUNET_JSON_PACK (
2145 : GNUNET_JSON_pack_string (
2146 : "session_id",
2147 : uc->parse_request.paivana.paivana_id),
2148 : GNUNET_JSON_pack_object_steal (
2149 : "order",
2150 : GNUNET_JSON_PACK (
2151 : GNUNET_JSON_pack_uint64 ("version",
2152 : 1),
2153 : GNUNET_JSON_pack_array_incref ("choices",
2154 : uc->compute_price.choices),
2155 : GNUNET_JSON_pack_string (
2156 : "summary",
2157 : NULL == uc->parse_request.summary
2158 : ? uc->template_contract.summary
2159 : : uc->parse_request.summary),
2160 : GNUNET_JSON_pack_string ("fulfillment_url",
2161 : uc->parse_request.paivana.website))));
2162 3 : }
2163 :
2164 :
2165 : static void
2166 19 : handle_phase_create_order (struct UseContext *uc)
2167 : {
2168 : json_t *order;
2169 :
2170 19 : GNUNET_assert (NULL == uc->ihc.request_body);
2171 19 : switch (uc->template_type)
2172 : {
2173 8 : case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
2174 8 : create_using_templates_fixed (uc);
2175 8 : break;
2176 3 : case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
2177 3 : create_using_templates_paivana (uc);
2178 3 : break;
2179 8 : case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
2180 8 : create_using_templates_inventory (uc);
2181 8 : break;
2182 0 : case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
2183 0 : GNUNET_assert (0);
2184 : }
2185 19 : order = json_object_get (uc->ihc.request_body,
2186 : "order");
2187 19 : GNUNET_assert (json_is_object (order));
2188 : /* A zero duration means that the template did not specify one. Leave the
2189 : deadline absent in that case so the regular order handler applies the
2190 : instance default. Older backends accepted FOREVER; also use the instance
2191 : default for such legacy templates instead of generating a forbidden
2192 : "never" deadline. */
2193 19 : if (GNUNET_TIME_relative_is_forever (
2194 : uc->template_contract.pay_duration))
2195 : {
2196 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2197 : "Template uses invalid infinite pay duration; "
2198 : "using instance default\n");
2199 : }
2200 19 : else if (! GNUNET_TIME_relative_is_zero (
2201 : uc->template_contract.pay_duration))
2202 : {
2203 12 : GNUNET_assert (
2204 : 0 ==
2205 : json_object_set_new (
2206 : order,
2207 : "pay_deadline",
2208 : GNUNET_JSON_from_timestamp (
2209 : GNUNET_TIME_relative_to_timestamp (
2210 : uc->template_contract.pay_duration))));
2211 : }
2212 19 : uc->phase++;
2213 19 : }
2214 :
2215 :
2216 : /* ***************** Main handler **************** */
2217 :
2218 : enum MHD_Result
2219 65 : TMH_post_using_templates_ID (
2220 : const struct TMH_RequestHandler *rh,
2221 : struct MHD_Connection *connection,
2222 : struct TMH_HandlerContext *hc)
2223 : {
2224 65 : struct UseContext *uc = hc->ctx;
2225 :
2226 : (void) rh;
2227 65 : if (NULL == uc)
2228 : {
2229 46 : uc = GNUNET_new (struct UseContext);
2230 46 : uc->hc = hc;
2231 46 : hc->ctx = uc;
2232 46 : hc->cc = &cleanup_use_context;
2233 46 : uc->ihc.instance = hc->instance;
2234 46 : uc->phase = USE_PHASE_PARSE_REQUEST;
2235 46 : uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
2236 : }
2237 :
2238 : while (1)
2239 : {
2240 365 : switch (uc->phase)
2241 : {
2242 46 : case USE_PHASE_PARSE_REQUEST:
2243 46 : handle_phase_parse_request (uc);
2244 46 : break;
2245 45 : case USE_PHASE_LOOKUP_TEMPLATE:
2246 45 : handle_phase_lookup_template (uc);
2247 45 : break;
2248 43 : case USE_PHASE_PARSE_TEMPLATE:
2249 43 : handle_phase_template_contract (uc);
2250 43 : break;
2251 43 : case USE_PHASE_DB_FETCH:
2252 43 : handle_phase_db_fetch (uc);
2253 43 : break;
2254 41 : case USE_PHASE_VERIFY:
2255 41 : handle_phase_verify (uc);
2256 41 : break;
2257 23 : case USE_PHASE_COMPUTE_PRICE:
2258 23 : handle_phase_compute_price (uc);
2259 23 : break;
2260 20 : case USE_PHASE_CHECK_TIP:
2261 20 : handle_phase_check_tip (uc);
2262 20 : break;
2263 20 : case USE_PHASE_CHECK_TOTAL:
2264 20 : handle_phase_check_total (uc);
2265 20 : break;
2266 19 : case USE_PHASE_CREATE_ORDER:
2267 19 : handle_phase_create_order (uc);
2268 19 : break;
2269 38 : case USE_PHASE_SUBMIT_ORDER:
2270 38 : return TMH_private_post_orders (
2271 : NULL, /* not even used */
2272 : connection,
2273 : &uc->ihc);
2274 27 : case USE_PHASE_FINISHED_MHD_YES:
2275 27 : return MHD_YES;
2276 0 : case USE_PHASE_FINISHED_MHD_NO:
2277 0 : return MHD_NO;
2278 : }
2279 : }
2280 : }
|