Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2025 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-private-orders.c
22 : * @brief the POST /orders handler
23 : * @author Christian Grothoff
24 : * @author Marcello Stanisci
25 : * @author Christian Blättler
26 : */
27 : #include "platform.h"
28 : #include <gnunet/gnunet_common.h>
29 : #include <gnunet/gnunet_db_lib.h>
30 : #include <gnunet/gnunet_json_lib.h>
31 : #include <gnunet/gnunet_time_lib.h>
32 : #include <jansson.h>
33 : #include <microhttpd.h>
34 : #include <string.h>
35 : #include <taler/taler_error_codes.h>
36 : #include <taler/taler_signatures.h>
37 : #include <taler/taler_json_lib.h>
38 : #include <taler/taler_dbevents.h>
39 : #include <taler/taler_util.h>
40 : #include <taler/taler_merchant_util.h>
41 : #include <time.h>
42 : #include "taler-merchant-httpd.h"
43 : #include "taler-merchant-httpd_exchanges.h"
44 : #include "taler-merchant-httpd_post-private-orders.h"
45 : #include "taler-merchant-httpd_get-exchanges.h"
46 : #include "taler-merchant-httpd_contract.h"
47 : #include "taler-merchant-httpd_helper.h"
48 : #include "taler-merchant-httpd_get-private-orders.h"
49 : #include "merchantdb_lib.h"
50 : #include "merchant-database/start.h"
51 : #include "merchant-database/event_listen.h"
52 : #include "merchant-database/event_notify.h"
53 : #include "merchant-database/preflight.h"
54 : #include "merchant-database/do_expire_locks.h"
55 : #include "merchant-database/get_missing_money_pot.h"
56 : #include "merchant-database/insert_order.h"
57 : #include "merchant-database/insert_order_lock.h"
58 : #include "merchant-database/insert_token_family_key.h"
59 : #include "merchant-database/get_order.h"
60 : #include "merchant-database/get_order_summary.h"
61 : #include "merchant-database/get_product.h"
62 : #include "merchant-database/get_token_family_key.h"
63 : #include "merchant-database/update_token_family_key_expiration.h"
64 : #include "merchant-database/iterate_token_family_keys.h"
65 : #include "merchant-database/iterate_donau_instances_filtered.h"
66 : #include "merchant-database/get_otp_device.h"
67 : #include "merchant-database/delete_inventory_lock.h"
68 :
69 :
70 : /**
71 : * How often do we retry the simple INSERT database transaction?
72 : */
73 : #define MAX_RETRIES 3
74 :
75 : /**
76 : * Maximum number of inventory products per order.
77 : */
78 : #define MAX_PRODUCTS 1024
79 :
80 : /**
81 : * What is the label under which we find/place the merchant's
82 : * jurisdiction in the locations list by default?
83 : */
84 : #define STANDARD_LABEL_MERCHANT_JURISDICTION "_mj"
85 :
86 : /**
87 : * What is the label under which we find/place the merchant's
88 : * address in the locations list by default?
89 : */
90 : #define STANDARD_LABEL_MERCHANT_ADDRESS "_ma"
91 :
92 : /**
93 : * How long do we wait at most for /keys from the exchange(s)?
94 : * Ensures that we do not block forever just because some exchange
95 : * fails to respond *or* because our taler-merchant-keyscheck
96 : * refuses a forced download.
97 : */
98 : #define MAX_KEYS_WAIT \
99 : GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
100 :
101 : /**
102 : * Generate the base URL for the given merchant instance.
103 : *
104 : * @param connection the MHD connection
105 : * @param instance_id the merchant instance ID
106 : * @returns the merchant instance's base URL
107 : */
108 : static char *
109 113 : make_merchant_base_url (struct MHD_Connection *connection,
110 : const char *instance_id)
111 : {
112 : struct GNUNET_Buffer buf;
113 :
114 113 : if (GNUNET_OK !=
115 113 : TMH_base_url_by_connection (connection,
116 : instance_id,
117 : &buf))
118 0 : return NULL;
119 113 : GNUNET_buffer_write_path (&buf,
120 : "");
121 113 : return GNUNET_buffer_reap_str (&buf);
122 : }
123 :
124 :
125 : /**
126 : * Information about a product we are supposed to add to the order
127 : * based on what we know it from our inventory.
128 : */
129 : struct InventoryProduct
130 : {
131 : /**
132 : * Identifier of the product in the inventory.
133 : */
134 : const char *product_id;
135 :
136 : /**
137 : * Number of units of the product to add to the order (integer part).
138 : */
139 : uint64_t quantity;
140 :
141 : /**
142 : * Fractional part of the quantity in units of 1/1000000 of the base value.
143 : */
144 : uint32_t quantity_frac;
145 :
146 : /**
147 : * True if the integer quantity field was missing in the request.
148 : */
149 : bool quantity_missing;
150 :
151 : /**
152 : * String representation of the quantity, if supplied.
153 : */
154 : const char *unit_quantity;
155 :
156 : /**
157 : * True if the string quantity field was missing in the request.
158 : */
159 : bool unit_quantity_missing;
160 :
161 : /**
162 : * Money pot associated with the product. 0 for none.
163 : */
164 : uint64_t product_money_pot;
165 :
166 : };
167 :
168 :
169 : /**
170 : * Handle for a rekey operation where we (re)request
171 : * the /keys from the exchange.
172 : */
173 : struct RekeyExchange
174 : {
175 : /**
176 : * Kept in a DLL.
177 : */
178 : struct RekeyExchange *prev;
179 :
180 : /**
181 : * Kept in a DLL.
182 : */
183 : struct RekeyExchange *next;
184 :
185 : /**
186 : * order this is for.
187 : */
188 : struct OrderContext *oc;
189 :
190 : /**
191 : * Base URL of the exchange.
192 : */
193 : char *url;
194 :
195 : /**
196 : * Request for keys.
197 : */
198 : struct TMH_EXCHANGES_KeysOperation *fo;
199 :
200 : };
201 :
202 :
203 : /**
204 : * Data structure where we evaluate the viability of a given
205 : * wire method for this order.
206 : */
207 : struct WireMethodCandidate
208 : {
209 : /**
210 : * Kept in a DLL.
211 : */
212 : struct WireMethodCandidate *next;
213 :
214 : /**
215 : * Kept in a DLL.
216 : */
217 : struct WireMethodCandidate *prev;
218 :
219 : /**
220 : * The wire method we are evaluating.
221 : */
222 : const struct TMH_WireMethod *wm;
223 :
224 : /**
225 : * List of exchanges to use when we use this wire method.
226 : */
227 : json_t *exchanges;
228 :
229 : /**
230 : * Set of maximum amounts that could be paid over all available exchanges
231 : * for this @a wm. Used to determine if this order creation requests exceeds
232 : * legal limits.
233 : */
234 : struct TALER_AmountSet total_exchange_limits;
235 :
236 : };
237 :
238 :
239 : /**
240 : * Information we keep per order we are processing.
241 : */
242 : struct OrderContext
243 : {
244 : /**
245 : * Information set in the #ORDER_PHASE_PARSE_REQUEST phase.
246 : */
247 : struct
248 : {
249 : /**
250 : * Order field of the request
251 : */
252 : json_t *order;
253 :
254 : /**
255 : * Set to how long refunds will be allowed.
256 : */
257 : struct GNUNET_TIME_Relative refund_delay;
258 :
259 : /**
260 : * RFC8905 payment target type to find a matching merchant account
261 : */
262 : const char *payment_target;
263 :
264 : /**
265 : * Shared key to use with @e pos_algorithm.
266 : */
267 : char *pos_key;
268 :
269 : /**
270 : * Selected algorithm (by template) when we are to
271 : * generate an OTP code for payment confirmation.
272 : */
273 : enum TALER_MerchantConfirmationAlgorithm pos_algorithm;
274 :
275 : /**
276 : * Hash of the POST request data, used to detect
277 : * idempotent requests.
278 : */
279 : struct TALER_MerchantPostDataHashP h_post_data;
280 :
281 : /**
282 : * Length of the @e inventory_products array.
283 : */
284 : unsigned int inventory_products_length;
285 :
286 : /**
287 : * Specifies that some products are to be included in the
288 : * order from the inventory. For these inventory management
289 : * is performed (so the products must be in stock).
290 : */
291 : struct InventoryProduct *inventory_products;
292 :
293 : /**
294 : * Length of the @e uuids array.
295 : */
296 : unsigned int uuids_length;
297 :
298 : /**
299 : * array of UUIDs used to reserve products from @a inventory_products.
300 : */
301 : struct GNUNET_Uuid *uuids;
302 :
303 : /**
304 : * Claim token for the request.
305 : */
306 : struct TALER_ClaimTokenP claim_token;
307 :
308 : /**
309 : * Session ID (optional) to use for the order.
310 : */
311 : const char *session_id;
312 :
313 : } parse_request;
314 :
315 : /**
316 : * Information set in the #ORDER_PHASE_PARSE_ORDER phase.
317 : */
318 : struct
319 : {
320 :
321 : /**
322 : * The main order data as provided by the client.
323 : */
324 : struct TALER_MERCHANT_Order *order;
325 :
326 : /**
327 : * Base URL of this merchant.
328 : */
329 : char *merchant_base_url;
330 :
331 : /**
332 : * Wire transfer round-up interval to apply.
333 : */
334 : enum GNUNET_TIME_RounderInterval wire_deadline_rounder;
335 :
336 : } parse_order;
337 :
338 : /**
339 : * Information set in the #ORDER_PHASE_PARSE_CHOICES phase.
340 : */
341 : struct
342 : {
343 : /**
344 : * Array of possible specific contracts the wallet/customer may choose
345 : * from by selecting the respective index when signing the deposit
346 : * confirmation.
347 : */
348 : struct TALER_MERCHANT_ContractChoice *choices;
349 :
350 : /**
351 : * Length of the @e choices array.
352 : */
353 : unsigned int choices_len;
354 :
355 : /**
356 : * Array of token families referenced in the contract.
357 : */
358 : struct TALER_MERCHANT_ContractTokenFamily *token_families;
359 :
360 : /**
361 : * Length of the @e token_families array.
362 : */
363 : unsigned int token_families_len;
364 : } parse_choices;
365 :
366 : /**
367 : * Information set in the #ORDER_PHASE_MERGE_INVENTORY phase.
368 : */
369 : struct
370 : {
371 : /**
372 : * Merged array of products in the @e order.
373 : */
374 : json_t *products;
375 : } merge_inventory;
376 :
377 : /**
378 : * Information set in the #ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
379 : */
380 : struct
381 : {
382 :
383 : /**
384 : * DLL of wire methods under evaluation.
385 : */
386 : struct WireMethodCandidate *wmc_head;
387 :
388 : /**
389 : * DLL of wire methods under evaluation.
390 : */
391 : struct WireMethodCandidate *wmc_tail;
392 :
393 : /**
394 : * Array of maximum amounts that appear in the contract choices
395 : * per currency.
396 : * Determines the maximum amounts that a client could pay for this
397 : * order and which we must thus make sure is acceptable for the
398 : * selected wire method/account if possible.
399 : */
400 : struct TALER_Amount *max_choice_limits;
401 :
402 : /**
403 : * Length of the @e max_choice_limits array.
404 : */
405 : unsigned int num_max_choice_limits;
406 :
407 : /**
408 : * Set to true if we may need an exchange. True if any amount is non-zero.
409 : */
410 : bool need_exchange;
411 :
412 : } add_payment_details;
413 :
414 : /**
415 : * Information set in the #ORDER_PHASE_SELECT_WIRE_METHOD phase.
416 : */
417 : struct
418 : {
419 :
420 : /**
421 : * Array of exchanges we find acceptable for this order and wire method.
422 : */
423 : json_t *exchanges;
424 :
425 : /**
426 : * Wire method (and our bank account) we have selected
427 : * to be included for this order.
428 : */
429 : const struct TMH_WireMethod *wm;
430 :
431 : } select_wire_method;
432 :
433 : /**
434 : * Information set in the #ORDER_PHASE_SET_EXCHANGES phase.
435 : */
436 : struct
437 : {
438 :
439 : /**
440 : * Forced requests to /keys to update our exchange
441 : * information.
442 : */
443 : struct RekeyExchange *pending_reload_head;
444 :
445 : /**
446 : * Forced requests to /keys to update our exchange
447 : * information.
448 : */
449 : struct RekeyExchange *pending_reload_tail;
450 :
451 : /**
452 : * How long do we wait at most until giving up on getting keys?
453 : */
454 : struct GNUNET_TIME_Absolute keys_timeout;
455 :
456 : /**
457 : * Task to wake us up on @e keys_timeout.
458 : */
459 : struct GNUNET_SCHEDULER_Task *wakeup_task;
460 :
461 : /**
462 : * Array of reasons why a particular exchange may be
463 : * limited or not be eligible.
464 : */
465 : json_t *exchange_rejections;
466 :
467 : /**
468 : * Did we previously force reloading of /keys from
469 : * all exchanges? Set to 'true' to prevent us from
470 : * doing it again (and again...).
471 : */
472 : bool forced_reload;
473 :
474 : /**
475 : * Did we find a working exchange?
476 : */
477 : bool exchange_ok;
478 :
479 : /**
480 : * Did we find an exchange that justifies
481 : * reloading keys?
482 : */
483 : bool promising_exchange;
484 :
485 : /**
486 : * Set to true once we have attempted to load exchanges
487 : * for the first time.
488 : */
489 : bool exchanges_tried;
490 :
491 : /**
492 : * Details depending on the contract version.
493 : */
494 : union
495 : {
496 :
497 : /**
498 : * Details for contract v0.
499 : */
500 : struct
501 : {
502 : /**
503 : * Maximum fee for @e order based on STEFAN curves.
504 : * Used to set @e max_fee if not provided as part of
505 : * @e order.
506 : */
507 : struct TALER_Amount max_stefan_fee;
508 :
509 : } v0;
510 :
511 : /**
512 : * Details for contract v1.
513 : */
514 : struct
515 : {
516 : /**
517 : * Maximum fee for @e order based on STEFAN curves by
518 : * contract choice.
519 : * Used to set @e max_fee if not provided as part of
520 : * @e order.
521 : */
522 : struct TALER_Amount *max_stefan_fees;
523 :
524 : } v1;
525 :
526 : } details;
527 :
528 : } set_exchanges;
529 :
530 : /**
531 : * Information set in the #ORDER_PHASE_SET_MAX_FEE phase.
532 : */
533 : struct
534 : {
535 :
536 : /**
537 : * Details depending on the contract version.
538 : */
539 : union
540 : {
541 :
542 : /**
543 : * Details for contract v0.
544 : */
545 : struct
546 : {
547 : /**
548 : * Maximum fee
549 : */
550 : struct TALER_Amount max_fee;
551 : } v0;
552 :
553 : /**
554 : * Details for contract v1.
555 : */
556 : struct
557 : {
558 : /**
559 : * Maximum fees by contract choice.
560 : */
561 : struct TALER_Amount *max_fees;
562 :
563 : } v1;
564 :
565 : } details;
566 : } set_max_fee;
567 :
568 : /**
569 : * Information set in the #ORDER_PHASE_EXECUTE_ORDER phase.
570 : */
571 : struct
572 : {
573 : /**
574 : * Which product (by offset) is out of stock, UINT_MAX if all were in-stock.
575 : */
576 : unsigned int out_of_stock_index;
577 :
578 : /**
579 : * Set to a previous claim token *if* @e idempotent
580 : * is also true.
581 : */
582 : struct TALER_ClaimTokenP token;
583 :
584 : /**
585 : * Set to true if the order was idempotent and there
586 : * was an equivalent one before.
587 : */
588 : bool idempotent;
589 :
590 : /**
591 : * Set to true if the order is in conflict with a
592 : * previous order with the same order ID.
593 : */
594 : bool conflict;
595 : } execute_order;
596 :
597 : struct
598 : {
599 : /**
600 : * Contract terms to store in the database.
601 : */
602 : json_t *contract;
603 : } serialize_order;
604 :
605 : /**
606 : * Connection of the request.
607 : */
608 : struct MHD_Connection *connection;
609 :
610 : /**
611 : * Kept in a DLL while suspended.
612 : */
613 : struct OrderContext *next;
614 :
615 : /**
616 : * Kept in a DLL while suspended.
617 : */
618 : struct OrderContext *prev;
619 :
620 : /**
621 : * Handler context for the request.
622 : */
623 : struct TMH_HandlerContext *hc;
624 :
625 : /**
626 : * #GNUNET_YES if suspended.
627 : */
628 : enum GNUNET_GenericReturnValue suspended;
629 :
630 : /**
631 : * Current phase of setting up the order.
632 : */
633 : enum
634 : {
635 : ORDER_PHASE_PARSE_REQUEST,
636 : ORDER_PHASE_PARSE_ORDER,
637 : ORDER_PHASE_PARSE_CHOICES,
638 : ORDER_PHASE_MERGE_INVENTORY,
639 : ORDER_PHASE_ADD_PAYMENT_DETAILS,
640 : ORDER_PHASE_SET_EXCHANGES,
641 : ORDER_PHASE_SELECT_WIRE_METHOD,
642 : ORDER_PHASE_SET_MAX_FEE,
643 : ORDER_PHASE_SERIALIZE_ORDER,
644 : ORDER_PHASE_SALT_FORGETTABLE,
645 : ORDER_PHASE_CHECK_CONTRACT,
646 : ORDER_PHASE_EXECUTE_ORDER,
647 :
648 : /**
649 : * Processing is done, we should return #MHD_YES.
650 : */
651 : ORDER_PHASE_FINISHED_MHD_YES,
652 :
653 : /**
654 : * Processing is done, we should return #MHD_NO.
655 : */
656 : ORDER_PHASE_FINISHED_MHD_NO
657 : } phase;
658 :
659 :
660 : };
661 :
662 :
663 : /**
664 : * Kept in a DLL while suspended.
665 : */
666 : static struct OrderContext *oc_head;
667 :
668 : /**
669 : * Kept in a DLL while suspended.
670 : */
671 : static struct OrderContext *oc_tail;
672 :
673 :
674 : void
675 20 : TMH_force_orders_resume ()
676 : {
677 : struct OrderContext *oc;
678 :
679 20 : while (NULL != (oc = oc_head))
680 : {
681 0 : GNUNET_CONTAINER_DLL_remove (oc_head,
682 : oc_tail,
683 : oc);
684 0 : oc->suspended = GNUNET_SYSERR;
685 0 : MHD_resume_connection (oc->connection);
686 : }
687 20 : }
688 :
689 :
690 : /**
691 : * Update the phase of @a oc based on @a mret.
692 : *
693 : * @param[in,out] oc order to update phase for
694 : * @param mret #MHD_NO to close with #MHD_NO
695 : * #MHD_YES to close with #MHD_YES
696 : */
697 : static void
698 121 : finalize_order (struct OrderContext *oc,
699 : enum MHD_Result mret)
700 : {
701 121 : oc->phase = (MHD_YES == mret)
702 : ? ORDER_PHASE_FINISHED_MHD_YES
703 121 : : ORDER_PHASE_FINISHED_MHD_NO;
704 121 : }
705 :
706 :
707 : /**
708 : * Update the phase of @a oc based on @a ret.
709 : *
710 : * @param[in,out] oc order to update phase for
711 : * @param ret #GNUNET_SYSERR to close with #MHD_NO
712 : * #GNUNET_NO to close with #MHD_YES
713 : * #GNUNET_OK is not allowed!
714 : */
715 : static void
716 0 : finalize_order2 (struct OrderContext *oc,
717 : enum GNUNET_GenericReturnValue ret)
718 : {
719 0 : GNUNET_assert (GNUNET_OK != ret);
720 0 : oc->phase = (GNUNET_NO == ret)
721 : ? ORDER_PHASE_FINISHED_MHD_YES
722 0 : : ORDER_PHASE_FINISHED_MHD_NO;
723 0 : }
724 :
725 :
726 : /**
727 : * Generate an error response for @a oc.
728 : *
729 : * @param[in,out] oc order context to respond to
730 : * @param http_status HTTP status code to set
731 : * @param ec error code to set
732 : * @param detail error message detail to set
733 : */
734 : static void
735 17 : reply_with_error (struct OrderContext *oc,
736 : unsigned int http_status,
737 : enum TALER_ErrorCode ec,
738 : const char *detail)
739 : {
740 : enum MHD_Result mret;
741 :
742 17 : mret = TALER_MHD_reply_with_error (oc->connection,
743 : http_status,
744 : ec,
745 : detail);
746 17 : finalize_order (oc,
747 : mret);
748 17 : }
749 :
750 :
751 : /**
752 : * Clean up memory used by @a wmc.
753 : *
754 : * @param[in,out] oc order context the WMC is part of
755 : * @param[in] wmc wire method candidate to free
756 : */
757 : static void
758 107 : free_wmc (struct OrderContext *oc,
759 : struct WireMethodCandidate *wmc)
760 : {
761 107 : GNUNET_CONTAINER_DLL_remove (oc->add_payment_details.wmc_head,
762 : oc->add_payment_details.wmc_tail,
763 : wmc);
764 107 : TALER_amount_set_free (&wmc->total_exchange_limits);
765 107 : json_decref (wmc->exchanges);
766 107 : GNUNET_free (wmc);
767 107 : }
768 :
769 :
770 : /**
771 : * Clean up memory used by @a cls.
772 : *
773 : * @param[in] cls the `struct OrderContext` to clean up
774 : */
775 : static void
776 121 : clean_order (void *cls)
777 : {
778 121 : struct OrderContext *oc = cls;
779 : struct RekeyExchange *rx;
780 :
781 228 : while (NULL != oc->add_payment_details.wmc_head)
782 107 : free_wmc (oc,
783 : oc->add_payment_details.wmc_head);
784 121 : while (NULL != (rx = oc->set_exchanges.pending_reload_head))
785 : {
786 0 : GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
787 : oc->set_exchanges.pending_reload_tail,
788 : rx);
789 0 : TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
790 0 : GNUNET_free (rx->url);
791 0 : GNUNET_free (rx);
792 : }
793 121 : GNUNET_array_grow (oc->add_payment_details.max_choice_limits,
794 : oc->add_payment_details.num_max_choice_limits,
795 : 0);
796 121 : if (NULL != oc->set_exchanges.wakeup_task)
797 : {
798 0 : GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
799 0 : oc->set_exchanges.wakeup_task = NULL;
800 : }
801 121 : if (NULL != oc->select_wire_method.exchanges)
802 : {
803 106 : json_decref (oc->select_wire_method.exchanges);
804 106 : oc->select_wire_method.exchanges = NULL;
805 : }
806 121 : if (NULL != oc->set_exchanges.exchange_rejections)
807 : {
808 0 : json_decref (oc->set_exchanges.exchange_rejections);
809 0 : oc->set_exchanges.exchange_rejections = NULL;
810 : }
811 121 : if (NULL != oc->parse_order.order)
812 : {
813 115 : switch (oc->parse_order.order->base->version)
814 : {
815 89 : case TALER_MERCHANT_CONTRACT_VERSION_0:
816 89 : break;
817 26 : case TALER_MERCHANT_CONTRACT_VERSION_1:
818 26 : GNUNET_free (oc->set_max_fee.details.v1.max_fees);
819 26 : GNUNET_free (oc->set_exchanges.details.v1.max_stefan_fees);
820 26 : break;
821 : }
822 115 : TALER_MERCHANT_order_free (oc->parse_order.order);
823 115 : oc->parse_order.order = NULL;
824 115 : GNUNET_free (oc->parse_order.merchant_base_url);
825 : }
826 121 : if (NULL != oc->merge_inventory.products)
827 : {
828 113 : json_decref (oc->merge_inventory.products);
829 113 : oc->merge_inventory.products = NULL;
830 : }
831 152 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
832 : {
833 31 : TALER_MERCHANT_contract_choice_free (&oc->parse_choices.choices[i]);
834 : }
835 121 : GNUNET_array_grow (oc->parse_choices.choices,
836 : oc->parse_choices.choices_len,
837 : 0);
838 130 : for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
839 : {
840 9 : TALER_MERCHANT_contract_token_family_free (
841 9 : &oc->parse_choices.token_families[i]);
842 : }
843 121 : GNUNET_array_grow (oc->parse_choices.token_families,
844 : oc->parse_choices.token_families_len,
845 : 0);
846 121 : GNUNET_array_grow (oc->parse_request.inventory_products,
847 : oc->parse_request.inventory_products_length,
848 : 0);
849 121 : GNUNET_array_grow (oc->parse_request.uuids,
850 : oc->parse_request.uuids_length,
851 : 0);
852 121 : GNUNET_free (oc->parse_request.pos_key);
853 121 : json_decref (oc->parse_request.order);
854 121 : json_decref (oc->serialize_order.contract);
855 121 : GNUNET_free (oc);
856 121 : }
857 :
858 :
859 : /* ***************** ORDER_PHASE_EXECUTE_ORDER **************** */
860 :
861 : /**
862 : * Compute the quantity (integer and fractional parts) of a product that is
863 : * actually available for a new order. This excludes units already sold,
864 : * lost, or currently reserved by locks (shopping carts and unpaid orders).
865 : *
866 : * @param pd product details with current totals/sold/lost/locked
867 : * @param[out] available_value remaining whole units (normalized, non-negative)
868 : * @param[out] available_frac remaining fractional units (0..TALER_MERCHANT_UNIT_FRAC_BASE-1)
869 : */
870 : static void
871 3 : compute_available_quantity (
872 : const struct TALER_MERCHANTDB_ProductDetails *pd,
873 : uint64_t *available_value,
874 : uint32_t *available_frac)
875 : {
876 : int64_t value;
877 : int64_t frac;
878 :
879 3 : GNUNET_assert (NULL != available_value);
880 3 : GNUNET_assert (NULL != available_frac);
881 :
882 3 : if ( (INT64_MAX == pd->total_stock) &&
883 0 : (INT32_MAX == pd->total_stock_frac) )
884 : {
885 0 : *available_value = pd->total_stock;
886 0 : *available_frac = pd->total_stock_frac;
887 0 : return;
888 : }
889 :
890 3 : value = (int64_t) pd->total_stock
891 3 : - (int64_t) pd->total_sold
892 3 : - (int64_t) pd->total_lost
893 3 : - (int64_t) pd->total_locked;
894 3 : frac = (int64_t) pd->total_stock_frac
895 3 : - (int64_t) pd->total_sold_frac
896 3 : - (int64_t) pd->total_lost_frac
897 3 : - (int64_t) pd->total_locked_frac;
898 :
899 3 : if (frac < 0)
900 : {
901 2 : int64_t borrow = ((-frac) + TALER_MERCHANT_UNIT_FRAC_BASE - 1)
902 : / TALER_MERCHANT_UNIT_FRAC_BASE;
903 :
904 2 : value -= borrow;
905 2 : frac += borrow * (int64_t) TALER_MERCHANT_UNIT_FRAC_BASE;
906 : }
907 1 : else if (frac >= TALER_MERCHANT_UNIT_FRAC_BASE)
908 : {
909 0 : int64_t carry = frac / TALER_MERCHANT_UNIT_FRAC_BASE;
910 :
911 0 : value += carry;
912 0 : frac -= carry * (int64_t) TALER_MERCHANT_UNIT_FRAC_BASE;
913 : }
914 :
915 3 : if (value < 0)
916 : {
917 0 : GNUNET_break (0);
918 0 : value = 0;
919 0 : frac = 0;
920 : }
921 :
922 3 : *available_value = (uint64_t) value;
923 3 : *available_frac = (uint32_t) frac;
924 : }
925 :
926 :
927 : /**
928 : * Execute the database transaction to setup the order.
929 : *
930 : * @param[in,out] oc order context
931 : * @return transaction status, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a uuids were insufficient to reserve required inventory
932 : */
933 : static enum GNUNET_DB_QueryStatus
934 106 : execute_transaction (struct OrderContext *oc)
935 : {
936 : enum GNUNET_DB_QueryStatus qs;
937 : struct GNUNET_TIME_Timestamp timestamp;
938 : uint64_t order_serial;
939 :
940 106 : if (GNUNET_OK !=
941 106 : TALER_MERCHANTDB_start (TMH_db,
942 : "insert_order"))
943 : {
944 0 : GNUNET_break (0);
945 0 : return GNUNET_DB_STATUS_HARD_ERROR;
946 : }
947 :
948 : /* Test if we already have an order with this id */
949 : {
950 : json_t *contract_terms;
951 : struct TALER_MerchantPostDataHashP orig_post;
952 :
953 106 : qs = TALER_MERCHANTDB_get_order (TMH_db,
954 106 : oc->hc->instance->settings.id,
955 106 : oc->parse_order.order->order_id,
956 : &oc->execute_order.token,
957 : &orig_post,
958 : &contract_terms);
959 : /* If yes, check for idempotency */
960 106 : if (0 > qs)
961 : {
962 0 : GNUNET_break (0);
963 0 : TALER_MERCHANTDB_rollback (TMH_db);
964 4 : return qs;
965 : }
966 106 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
967 : {
968 4 : TALER_MERCHANTDB_rollback (TMH_db);
969 4 : json_decref (contract_terms);
970 : /* Comparing the contract terms is sufficient because all the other
971 : params get added to it at some point. */
972 4 : if (0 == GNUNET_memcmp (&orig_post,
973 : &oc->parse_request.h_post_data))
974 : {
975 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
976 : "Order creation idempotent\n");
977 2 : oc->execute_order.idempotent = true;
978 2 : return qs;
979 : }
980 2 : GNUNET_break_op (0);
981 2 : oc->execute_order.conflict = true;
982 2 : return qs;
983 : }
984 : }
985 :
986 : /* Setup order */
987 102 : qs = TALER_MERCHANTDB_insert_order (TMH_db,
988 102 : oc->hc->instance->settings.id,
989 102 : oc->parse_order.order->order_id,
990 : oc->parse_request.session_id,
991 102 : &oc->parse_request.h_post_data,
992 102 : oc->parse_order.order->pay_deadline,
993 102 : &oc->parse_request.claim_token,
994 102 : oc->serialize_order.contract, /* called 'contract terms' at database. */
995 102 : oc->parse_request.pos_key,
996 : oc->parse_request.pos_algorithm);
997 102 : if (qs <= 0)
998 : {
999 : /* qs == 0: probably instance does not exist (anymore) */
1000 0 : TALER_MERCHANTDB_rollback (TMH_db);
1001 0 : return qs;
1002 : }
1003 : /* Migrate locks from UUIDs to new order: first release old locks */
1004 105 : for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
1005 : {
1006 3 : qs = TALER_MERCHANTDB_delete_inventory_lock (TMH_db,
1007 3 : &oc->parse_request.uuids[i]);
1008 3 : if (qs < 0)
1009 : {
1010 0 : TALER_MERCHANTDB_rollback (TMH_db);
1011 0 : return qs;
1012 : }
1013 : /* qs == 0 is OK here, that just means we did not HAVE any lock under this
1014 : UUID */
1015 : }
1016 : /* Migrate locks from UUIDs to new order: acquire new locks
1017 : (note: this can basically ONLY fail on serializability OR
1018 : because the UUID locks were insufficient for the desired
1019 : quantities). */
1020 124 : for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
1021 : {
1022 25 : qs = TALER_MERCHANTDB_insert_order_lock (
1023 : TMH_db,
1024 25 : oc->hc->instance->settings.id,
1025 25 : oc->parse_order.order->order_id,
1026 25 : oc->parse_request.inventory_products[i].product_id,
1027 25 : oc->parse_request.inventory_products[i].quantity,
1028 25 : oc->parse_request.inventory_products[i].quantity_frac);
1029 25 : if (qs < 0)
1030 : {
1031 0 : TALER_MERCHANTDB_rollback (TMH_db);
1032 0 : return qs;
1033 : }
1034 25 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1035 : {
1036 : /* qs == 0: lock acquisition failed due to insufficient stocks */
1037 3 : TALER_MERCHANTDB_rollback (TMH_db);
1038 3 : oc->execute_order.out_of_stock_index = i; /* indicate which product is causing the issue */
1039 3 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1040 : }
1041 : }
1042 99 : oc->execute_order.out_of_stock_index = UINT_MAX;
1043 :
1044 : /* Get the order serial and timestamp for the order we just created to
1045 : update long-poll clients. */
1046 99 : qs = TALER_MERCHANTDB_get_order_summary (
1047 : TMH_db,
1048 99 : oc->hc->instance->settings.id,
1049 99 : oc->parse_order.order->order_id,
1050 : ×tamp,
1051 : &order_serial);
1052 99 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
1053 : {
1054 0 : TALER_MERCHANTDB_rollback (TMH_db);
1055 0 : return qs;
1056 : }
1057 :
1058 : {
1059 : json_t *jhook;
1060 :
1061 99 : jhook = GNUNET_JSON_PACK (
1062 : GNUNET_JSON_pack_string ("order_id",
1063 : oc->parse_order.order->order_id),
1064 : GNUNET_JSON_pack_object_incref ("contract",
1065 : oc->serialize_order.contract),
1066 : GNUNET_JSON_pack_string ("instance_id",
1067 : oc->hc->instance->settings.id)
1068 : );
1069 99 : GNUNET_assert (NULL != jhook);
1070 99 : qs = TMH_trigger_webhook (oc->hc->instance->settings.id,
1071 : "order_created",
1072 : jhook);
1073 99 : json_decref (jhook);
1074 99 : if (0 > qs)
1075 : {
1076 0 : TALER_MERCHANTDB_rollback (TMH_db);
1077 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
1078 0 : return qs;
1079 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
1080 0 : reply_with_error (oc,
1081 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1082 : TALER_EC_GENERIC_DB_STORE_FAILED,
1083 : "failed to trigger webhooks");
1084 0 : return qs;
1085 : }
1086 : }
1087 :
1088 99 : TMH_notify_order_change (oc->hc->instance,
1089 : TMH_OSF_NONE,
1090 : timestamp,
1091 : order_serial);
1092 : /* finally, commit transaction (note: if it fails, we ALSO re-acquire
1093 : the UUID locks, which is exactly what we want) */
1094 99 : qs = TALER_MERCHANTDB_commit (TMH_db);
1095 99 : if (0 > qs)
1096 0 : return qs;
1097 99 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* 1 == success! */
1098 : }
1099 :
1100 :
1101 : /**
1102 : * The request was successful, generate the #MHD_HTTP_OK response.
1103 : *
1104 : * @param[in,out] oc context to update
1105 : * @param claim_token claim token to use, NULL if none
1106 : */
1107 : static void
1108 101 : yield_success_response (struct OrderContext *oc,
1109 : const struct TALER_ClaimTokenP *claim_token)
1110 : {
1111 : enum MHD_Result ret;
1112 :
1113 101 : ret = TALER_MHD_REPLY_JSON_PACK (
1114 : oc->connection,
1115 : MHD_HTTP_OK,
1116 : GNUNET_JSON_pack_string ("order_id",
1117 : oc->parse_order.order->order_id),
1118 : GNUNET_JSON_pack_timestamp ("pay_deadline",
1119 : oc->parse_order.order->pay_deadline),
1120 : GNUNET_JSON_pack_allow_null (
1121 : GNUNET_JSON_pack_data_auto (
1122 : "token",
1123 : claim_token)));
1124 101 : finalize_order (oc,
1125 : ret);
1126 101 : }
1127 :
1128 :
1129 : /**
1130 : * Transform an order into a proposal and store it in the
1131 : * database. Write the resulting proposal or an error message
1132 : * of a MHD connection.
1133 : *
1134 : * @param[in,out] oc order context
1135 : */
1136 : static void
1137 106 : phase_execute_order (struct OrderContext *oc)
1138 : {
1139 106 : const struct TALER_MERCHANTDB_InstanceSettings *settings =
1140 106 : &oc->hc->instance->settings;
1141 : enum GNUNET_DB_QueryStatus qs;
1142 :
1143 106 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1144 : "Executing database transaction to create order '%s' for instance '%s'\n",
1145 : oc->parse_order.order->order_id,
1146 : settings->id);
1147 106 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
1148 : {
1149 106 : TALER_MERCHANTDB_preflight (TMH_db);
1150 106 : qs = execute_transaction (oc);
1151 106 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
1152 106 : break;
1153 : }
1154 106 : if (0 >= qs)
1155 : {
1156 : /* Special report if retries insufficient */
1157 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
1158 : {
1159 0 : GNUNET_break (0);
1160 0 : reply_with_error (oc,
1161 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1162 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
1163 : NULL);
1164 0 : return;
1165 : }
1166 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1167 : {
1168 : /* should be: contract (!) with same order ID
1169 : already exists */
1170 0 : reply_with_error (
1171 : oc,
1172 : MHD_HTTP_CONFLICT,
1173 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
1174 0 : oc->parse_order.order->order_id);
1175 0 : return;
1176 : }
1177 : /* Other hard transaction error (disk full, etc.) */
1178 0 : GNUNET_break (0);
1179 0 : reply_with_error (
1180 : oc,
1181 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1182 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
1183 : NULL);
1184 0 : return;
1185 : }
1186 :
1187 : /* DB transaction succeeded, check for idempotent */
1188 106 : if (oc->execute_order.idempotent)
1189 : {
1190 2 : yield_success_response (oc,
1191 2 : GNUNET_is_zero (&oc->execute_order.token)
1192 : ? NULL
1193 : : &oc->execute_order.token);
1194 2 : return;
1195 : }
1196 104 : if (oc->execute_order.conflict)
1197 : {
1198 2 : reply_with_error (
1199 : oc,
1200 : MHD_HTTP_CONFLICT,
1201 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
1202 2 : oc->parse_order.order->order_id);
1203 2 : return;
1204 : }
1205 :
1206 : /* DB transaction succeeded, check for out-of-stock */
1207 102 : if (oc->execute_order.out_of_stock_index < UINT_MAX)
1208 : {
1209 : /* We had a product that has insufficient quantities,
1210 : generate the details for the response. */
1211 : struct TALER_MERCHANTDB_ProductDetails pd;
1212 : enum MHD_Result ret;
1213 : const struct InventoryProduct *ip;
1214 3 : size_t num_categories = 0;
1215 3 : uint64_t *categories = NULL;
1216 : uint64_t available_quantity;
1217 : uint32_t available_quantity_frac;
1218 : char requested_quantity_buf[64];
1219 : char available_quantity_buf[64];
1220 :
1221 3 : ip = &oc->parse_request.inventory_products[
1222 3 : oc->execute_order.out_of_stock_index];
1223 3 : memset (&pd,
1224 : 0,
1225 : sizeof (pd));
1226 3 : qs = TALER_MERCHANTDB_get_product (
1227 : TMH_db,
1228 3 : oc->hc->instance->settings.id,
1229 3 : ip->product_id,
1230 : &pd,
1231 : &num_categories,
1232 : &categories);
1233 3 : switch (qs)
1234 : {
1235 3 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1236 3 : GNUNET_free (categories);
1237 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1238 : "Order creation failed: product out of stock\n");
1239 :
1240 3 : compute_available_quantity (&pd,
1241 : &available_quantity,
1242 : &available_quantity_frac);
1243 3 : TALER_MERCHANT_vk_format_fractional_string (
1244 : TALER_MERCHANT_VK_QUANTITY,
1245 3 : ip->quantity,
1246 3 : ip->quantity_frac,
1247 : sizeof (requested_quantity_buf),
1248 : requested_quantity_buf);
1249 3 : TALER_MERCHANT_vk_format_fractional_string (
1250 : TALER_MERCHANT_VK_QUANTITY,
1251 : available_quantity,
1252 : available_quantity_frac,
1253 : sizeof (available_quantity_buf),
1254 : available_quantity_buf);
1255 3 : ret = TALER_MHD_REPLY_JSON_PACK (
1256 : oc->connection,
1257 : MHD_HTTP_GONE,
1258 : GNUNET_JSON_pack_string (
1259 : "product_id",
1260 : ip->product_id),
1261 : GNUNET_JSON_pack_uint64 (
1262 : "requested_quantity",
1263 : ip->quantity),
1264 : GNUNET_JSON_pack_string (
1265 : "unit_requested_quantity",
1266 : requested_quantity_buf),
1267 : GNUNET_JSON_pack_uint64 (
1268 : "available_quantity",
1269 : available_quantity),
1270 : GNUNET_JSON_pack_string (
1271 : "unit_available_quantity",
1272 : available_quantity_buf),
1273 : GNUNET_JSON_pack_allow_null (
1274 : GNUNET_JSON_pack_timestamp (
1275 : "restock_expected",
1276 : pd.next_restock)));
1277 3 : TALER_MERCHANTDB_product_details_free (&pd);
1278 3 : finalize_order (oc,
1279 : ret);
1280 3 : return;
1281 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1282 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1283 : "Order creation failed: unknown product out of stock\n");
1284 0 : finalize_order (oc,
1285 0 : TALER_MHD_REPLY_JSON_PACK (
1286 : oc->connection,
1287 : MHD_HTTP_GONE,
1288 : GNUNET_JSON_pack_string (
1289 : "product_id",
1290 : ip->product_id),
1291 : GNUNET_JSON_pack_uint64 (
1292 : "requested_quantity",
1293 : ip->quantity),
1294 : GNUNET_JSON_pack_uint64 (
1295 : "available_quantity",
1296 : 0)));
1297 0 : return;
1298 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1299 0 : GNUNET_break (0);
1300 0 : reply_with_error (
1301 : oc,
1302 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1303 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
1304 : NULL);
1305 0 : return;
1306 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1307 0 : GNUNET_break (0);
1308 0 : reply_with_error (
1309 : oc,
1310 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1311 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1312 : NULL);
1313 0 : return;
1314 : }
1315 0 : GNUNET_break (0);
1316 0 : oc->phase = ORDER_PHASE_FINISHED_MHD_NO;
1317 0 : return;
1318 : } /* end 'out of stock' case */
1319 :
1320 : /* Everything in-stock, generate positive response */
1321 99 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1322 : "Order creation succeeded\n");
1323 99 : yield_success_response (oc,
1324 99 : GNUNET_is_zero (&oc->parse_request.claim_token)
1325 : ? NULL
1326 : : &oc->parse_request.claim_token);
1327 : }
1328 :
1329 :
1330 : /* ***************** ORDER_PHASE_CHECK_CONTRACT **************** */
1331 :
1332 :
1333 : /**
1334 : * Check that the contract is now well-formed. Upon success, continue
1335 : * processing with execute_order().
1336 : *
1337 : * @param[in,out] oc order context
1338 : */
1339 : static void
1340 106 : phase_check_contract (struct OrderContext *oc)
1341 : {
1342 : struct TALER_PrivateContractHashP h_control;
1343 :
1344 106 : switch (TALER_JSON_contract_hash (oc->serialize_order.contract,
1345 : &h_control))
1346 : {
1347 0 : case GNUNET_SYSERR:
1348 0 : GNUNET_break (0);
1349 0 : reply_with_error (
1350 : oc,
1351 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1352 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1353 : "could not compute hash of serialized order");
1354 106 : return;
1355 0 : case GNUNET_NO:
1356 0 : GNUNET_break_op (0);
1357 0 : reply_with_error (
1358 : oc,
1359 : MHD_HTTP_BAD_REQUEST,
1360 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1361 : "order contained unallowed values");
1362 0 : return;
1363 106 : case GNUNET_OK:
1364 106 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1365 : "Contract hash is %s\n",
1366 : GNUNET_h2s (&h_control.hash));
1367 106 : oc->phase++;
1368 106 : return;
1369 : }
1370 0 : GNUNET_assert (0);
1371 : }
1372 :
1373 :
1374 : /* ***************** ORDER_PHASE_SALT_FORGETTABLE **************** */
1375 :
1376 :
1377 : /**
1378 : * Modify the final contract terms adding salts for
1379 : * items that are forgettable.
1380 : *
1381 : * @param[in,out] oc order context
1382 : */
1383 : static void
1384 106 : phase_salt_forgettable (struct OrderContext *oc)
1385 : {
1386 106 : if (GNUNET_OK !=
1387 106 : TALER_JSON_contract_seed_forgettable (oc->parse_request.order,
1388 : oc->serialize_order.contract))
1389 : {
1390 0 : GNUNET_break_op (0);
1391 0 : reply_with_error (
1392 : oc,
1393 : MHD_HTTP_BAD_REQUEST,
1394 : TALER_EC_GENERIC_JSON_INVALID,
1395 : "could not compute hash of order due to bogus forgettable fields");
1396 0 : return;
1397 : }
1398 106 : oc->phase++;
1399 : }
1400 :
1401 :
1402 : /* ***************** ORDER_PHASE_SERIALIZE_ORDER **************** */
1403 :
1404 : /**
1405 : * Get rounded time interval. @a start is calculated by rounding
1406 : * @a ts down to the nearest multiple of @a precision.
1407 : *
1408 : * @param precision rounding precision.
1409 : * year, month, day, hour, minute are supported.
1410 : * @param ts timestamp to round
1411 : * @param[out] start start of the interval
1412 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1413 : */
1414 : static enum GNUNET_GenericReturnValue
1415 8 : get_rounded_time_interval_down (struct GNUNET_TIME_Relative precision,
1416 : struct GNUNET_TIME_Timestamp ts,
1417 : struct GNUNET_TIME_Timestamp *start)
1418 : {
1419 : enum GNUNET_TIME_RounderInterval ri;
1420 :
1421 8 : ri = GNUNET_TIME_relative_to_round_interval (precision);
1422 8 : if ( (GNUNET_TIME_RI_NONE == ri) &&
1423 0 : (! GNUNET_TIME_relative_is_zero (precision)) )
1424 : {
1425 0 : *start = ts;
1426 0 : return GNUNET_SYSERR;
1427 : }
1428 8 : *start = GNUNET_TIME_absolute_to_timestamp (
1429 : GNUNET_TIME_round_down (ts.abs_time,
1430 : ri));
1431 8 : return GNUNET_OK;
1432 : }
1433 :
1434 :
1435 : /**
1436 : * Get rounded time interval. @a start is calculated by rounding
1437 : * @a ts up to the nearest multiple of @a precision.
1438 : *
1439 : * @param precision rounding precision.
1440 : * year, month, day, hour, minute are supported.
1441 : * @param ts timestamp to round
1442 : * @param[out] start start of the interval
1443 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1444 : */
1445 : static enum GNUNET_GenericReturnValue
1446 0 : get_rounded_time_interval_up (struct GNUNET_TIME_Relative precision,
1447 : struct GNUNET_TIME_Timestamp ts,
1448 : struct GNUNET_TIME_Timestamp *start)
1449 : {
1450 : enum GNUNET_TIME_RounderInterval ri;
1451 :
1452 0 : ri = GNUNET_TIME_relative_to_round_interval (precision);
1453 0 : if ( (GNUNET_TIME_RI_NONE == ri) &&
1454 0 : (! GNUNET_TIME_relative_is_zero (precision)) )
1455 : {
1456 0 : *start = ts;
1457 0 : return GNUNET_SYSERR;
1458 : }
1459 0 : *start = GNUNET_TIME_absolute_to_timestamp (
1460 : GNUNET_TIME_round_up (ts.abs_time,
1461 : ri));
1462 0 : return GNUNET_OK;
1463 : }
1464 :
1465 :
1466 : /**
1467 : * Find the family entry for the family of the given @a slug
1468 : * in @a oc.
1469 : *
1470 : * @param[in] oc order context to search
1471 : * @param slug slug to search for
1472 : * @return NULL if @a slug was not found
1473 : */
1474 : static struct TALER_MERCHANT_ContractTokenFamily *
1475 21 : find_family (const struct OrderContext *oc,
1476 : const char *slug)
1477 : {
1478 21 : for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
1479 : {
1480 8 : if (0 == strcmp (oc->parse_choices.token_families[i].slug,
1481 : slug))
1482 : {
1483 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1484 : "Token family %s already in order\n",
1485 : slug);
1486 8 : return &oc->parse_choices.token_families[i];
1487 : }
1488 : }
1489 13 : return NULL;
1490 : }
1491 :
1492 :
1493 : /**
1494 : * Function called with each applicable family key that should
1495 : * be added to the respective token family of the order.
1496 : *
1497 : * @param cls a `struct OrderContext *` to expand
1498 : * @param tfkd token family key details to add to the contract
1499 : */
1500 : static void
1501 9 : add_family_key (void *cls,
1502 : const struct TALER_MERCHANTDB_TokenFamilyKeyDetails *tfkd)
1503 : {
1504 9 : struct OrderContext *oc = cls;
1505 9 : const struct TALER_MERCHANTDB_TokenFamilyDetails *tf = &tfkd->token_family;
1506 : struct TALER_MERCHANT_ContractTokenFamily *family;
1507 :
1508 9 : family = find_family (oc,
1509 9 : tf->slug);
1510 9 : if (NULL == family)
1511 : {
1512 : /* Family not yet in our contract terms, create new entry */
1513 45 : struct TALER_MERCHANT_ContractTokenFamily new_family = {
1514 9 : .slug = GNUNET_strdup (tf->slug),
1515 9 : .name = GNUNET_strdup (tf->name),
1516 9 : .description = GNUNET_strdup (tf->description),
1517 9 : .description_i18n = json_incref (tf->description_i18n),
1518 : };
1519 :
1520 9 : switch (tf->kind)
1521 : {
1522 8 : case TALER_MERCHANTDB_TFK_Subscription:
1523 : {
1524 8 : json_t *tdomains = json_object_get (tf->extra_data,
1525 : "trusted_domains");
1526 : json_t *dom;
1527 : size_t i;
1528 :
1529 8 : new_family.kind = TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION;
1530 8 : new_family.critical = true;
1531 : new_family.details.subscription.trusted_domains_len
1532 8 : = json_array_size (tdomains);
1533 8 : GNUNET_assert (new_family.details.subscription.trusted_domains_len
1534 : < UINT_MAX);
1535 : new_family.details.subscription.trusted_domains
1536 8 : = GNUNET_new_array (
1537 : new_family.details.subscription.trusted_domains_len,
1538 : char *);
1539 8 : json_array_foreach (tdomains, i, dom)
1540 : {
1541 : const char *val;
1542 :
1543 0 : val = json_string_value (dom);
1544 0 : GNUNET_break (NULL != val);
1545 0 : if (NULL != val)
1546 0 : new_family.details.subscription.trusted_domains[i]
1547 0 : = GNUNET_strdup (val);
1548 : }
1549 8 : break;
1550 : }
1551 1 : case TALER_MERCHANTDB_TFK_Discount:
1552 : {
1553 1 : json_t *edomains = json_object_get (tf->extra_data,
1554 : "expected_domains");
1555 : json_t *dom;
1556 : size_t i;
1557 :
1558 1 : new_family.kind = TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT;
1559 1 : new_family.critical = false;
1560 : new_family.details.discount.expected_domains_len
1561 1 : = json_array_size (edomains);
1562 1 : GNUNET_assert (new_family.details.discount.expected_domains_len
1563 : < UINT_MAX);
1564 : new_family.details.discount.expected_domains
1565 1 : = GNUNET_new_array (
1566 : new_family.details.discount.expected_domains_len,
1567 : char *);
1568 1 : json_array_foreach (edomains, i, dom)
1569 : {
1570 : const char *val;
1571 :
1572 0 : val = json_string_value (dom);
1573 0 : GNUNET_break (NULL != val);
1574 0 : if (NULL != val)
1575 0 : new_family.details.discount.expected_domains[i]
1576 0 : = GNUNET_strdup (val);
1577 : }
1578 1 : break;
1579 : }
1580 : }
1581 9 : GNUNET_array_append (oc->parse_choices.token_families,
1582 : oc->parse_choices.token_families_len,
1583 : new_family);
1584 9 : family = &oc->parse_choices.token_families[
1585 9 : oc->parse_choices.token_families_len - 1];
1586 : }
1587 9 : if (NULL == tfkd->pub.public_key)
1588 5 : return;
1589 4 : for (unsigned int i = 0; i<family->keys_len; i++)
1590 : {
1591 : /* Note: cmp() returns 0 when the keys are EQUAL (memcmp-style). */
1592 0 : if (0 == TALER_token_issue_pub_cmp (&family->keys[i].pub,
1593 : &tfkd->pub))
1594 : {
1595 : /* A matching key is already in the list. */
1596 0 : return;
1597 : }
1598 : }
1599 :
1600 : {
1601 : struct TALER_MERCHANT_ContractTokenFamilyKey key;
1602 :
1603 4 : TALER_token_issue_pub_copy (&key.pub,
1604 : &tfkd->pub);
1605 4 : key.valid_after = tfkd->signature_validity_start;
1606 4 : key.valid_before = tfkd->signature_validity_end;
1607 4 : GNUNET_array_append (family->keys,
1608 : family->keys_len,
1609 : key);
1610 : }
1611 : }
1612 :
1613 :
1614 : /**
1615 : * Check if the token family with the given @a slug is already present in the
1616 : * list of token families for this order. If not, fetch its details and add it
1617 : * to the list.
1618 : *
1619 : * @param[in,out] oc order context
1620 : * @param slug slug of the token family
1621 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1622 : */
1623 : static enum GNUNET_GenericReturnValue
1624 5 : add_input_token_family (struct OrderContext *oc,
1625 : const char *slug)
1626 : {
1627 5 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
1628 5 : struct GNUNET_TIME_Timestamp end = oc->parse_order.order->pay_deadline;
1629 : enum GNUNET_DB_QueryStatus qs;
1630 5 : enum TALER_ErrorCode ec = TALER_EC_INVALID; /* make compiler happy */
1631 5 : unsigned int http_status = 0; /* make compiler happy */
1632 :
1633 5 : qs = TALER_MERCHANTDB_iterate_token_family_keys (
1634 : TMH_db,
1635 5 : oc->hc->instance->settings.id,
1636 : slug,
1637 : now,
1638 : end,
1639 : &add_family_key,
1640 : oc);
1641 5 : switch (qs)
1642 : {
1643 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1644 0 : GNUNET_break (0);
1645 0 : http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
1646 0 : ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
1647 0 : break;
1648 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1649 0 : GNUNET_break (0);
1650 0 : http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
1651 0 : ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
1652 0 : break;
1653 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1654 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1655 : "Input token family slug %s unknown\n",
1656 : slug);
1657 0 : http_status = MHD_HTTP_NOT_FOUND;
1658 0 : ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
1659 0 : break;
1660 5 : default: /* one or more results are all OK */
1661 5 : return GNUNET_OK;
1662 : }
1663 0 : reply_with_error (oc,
1664 : http_status,
1665 : ec,
1666 : slug);
1667 0 : return GNUNET_SYSERR;
1668 : }
1669 :
1670 :
1671 : /**
1672 : * Find the index of a key in the @a family that is valid at
1673 : * the time @a valid_at.
1674 : *
1675 : * @param family to search
1676 : * @param valid_at time when the key must be valid
1677 : * @param[out] key_index index to initialize
1678 : * @return #GNUNET_OK if a matching key was found
1679 : */
1680 : static enum GNUNET_GenericReturnValue
1681 4 : find_key_index (struct TALER_MERCHANT_ContractTokenFamily *family,
1682 : struct GNUNET_TIME_Timestamp valid_at,
1683 : unsigned int *key_index)
1684 : {
1685 4 : for (unsigned int i = 0; i<family->keys_len; i++)
1686 : {
1687 4 : if ( (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
1688 : <=,
1689 4 : valid_at)) &&
1690 4 : (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_before,
1691 : >=,
1692 : valid_at)) )
1693 : {
1694 : /* The token family and a matching key already exist. */
1695 4 : *key_index = i;
1696 4 : return GNUNET_OK;
1697 : }
1698 : }
1699 0 : return GNUNET_NO;
1700 : }
1701 :
1702 :
1703 : /**
1704 : * Create fresh key pair based on @a cipher_spec.
1705 : *
1706 : * @param cipher_spec which kind of key pair should we generate
1707 : * @param[out] priv set to new private key
1708 : * @param[out] pub set to new public key
1709 : * @return #GNUNET_OK on success
1710 : */
1711 : static enum GNUNET_GenericReturnValue
1712 4 : create_key (const char *cipher_spec,
1713 : struct TALER_TokenIssuePrivateKey *priv,
1714 : struct TALER_TokenIssuePublicKey *pub)
1715 : {
1716 : unsigned int len;
1717 : char dummy;
1718 :
1719 4 : if (0 == strcmp ("cs",
1720 : cipher_spec))
1721 : {
1722 0 : GNUNET_CRYPTO_blind_sign_keys_create (
1723 : &priv->private_key,
1724 : &pub->public_key,
1725 : GNUNET_CRYPTO_BSA_CS);
1726 0 : return GNUNET_OK;
1727 : }
1728 4 : if (1 ==
1729 4 : sscanf (cipher_spec,
1730 : "rsa(%u)%c",
1731 : &len,
1732 : &dummy))
1733 : {
1734 4 : GNUNET_CRYPTO_blind_sign_keys_create (
1735 : &priv->private_key,
1736 : &pub->public_key,
1737 : GNUNET_CRYPTO_BSA_RSA,
1738 : len);
1739 4 : return GNUNET_OK;
1740 : }
1741 0 : return GNUNET_SYSERR;
1742 : }
1743 :
1744 :
1745 : /**
1746 : * Check if the token family with the given @a slug is already present in the
1747 : * list of token families for this order. If not, fetch its details and add it
1748 : * to the list. Also checks if there is a public key with that expires after
1749 : * the payment deadline. If a key covering @a valid_at exists but its private
1750 : * key would be deleted before the payment deadline, the lifetime of that
1751 : * private key is extended; only if no key covers @a valid_at at all do we
1752 : * generate a new key pair and store it in the database.
1753 : *
1754 : * @param[in,out] oc order context
1755 : * @param slug slug of the token family
1756 : * @param valid_at time when the token returned must be valid
1757 : * @param[out] key_index set to the index of the respective public
1758 : * key in the @a slug's token family keys array.
1759 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
1760 : */
1761 : static enum GNUNET_GenericReturnValue
1762 8 : add_output_token_family (struct OrderContext *oc,
1763 : const char *slug,
1764 : struct GNUNET_TIME_Timestamp valid_at,
1765 : unsigned int *key_index)
1766 : {
1767 : struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
1768 : struct TALER_MERCHANT_ContractTokenFamily *family;
1769 : enum GNUNET_DB_QueryStatus qs;
1770 :
1771 : /* We are about to promise a token of this family, so the private key
1772 : covering @a valid_at must survive until we sign at the pay deadline. If
1773 : an existing key covers the validity period but was minted for an order
1774 : with an earlier pay deadline, extend its lifetime instead of minting a
1775 : second key for the very same validity period: the key lookups below (and
1776 : find_key_index()) would otherwise consider that key missing and we would
1777 : end up listing two keys for one validity period in the contract terms. */
1778 8 : qs = TALER_MERCHANTDB_update_token_family_key_expiration (
1779 : TMH_db,
1780 8 : oc->hc->instance->settings.id,
1781 : slug,
1782 : valid_at,
1783 8 : oc->parse_order.order->pay_deadline);
1784 8 : switch (qs)
1785 : {
1786 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1787 0 : GNUNET_break (0);
1788 0 : reply_with_error (oc,
1789 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1790 : TALER_EC_GENERIC_DB_STORE_FAILED,
1791 : "update_token_family_key_expiration");
1792 0 : return GNUNET_SYSERR;
1793 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1794 : /* Single-statement transaction shouldn't possibly cause serialization errors.
1795 : Thus treating like a hard error. */
1796 0 : GNUNET_break (0);
1797 0 : reply_with_error (oc,
1798 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1799 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
1800 : "update_token_family_key_expiration");
1801 0 : return GNUNET_SYSERR;
1802 8 : default:
1803 : /* No key needed extending, or one/more were extended; either is fine. */
1804 8 : break;
1805 : }
1806 8 : family = find_family (oc,
1807 : slug);
1808 12 : if ( (NULL != family) &&
1809 : (GNUNET_OK ==
1810 4 : find_key_index (family,
1811 : valid_at,
1812 : key_index)) )
1813 4 : return GNUNET_OK;
1814 4 : qs = TALER_MERCHANTDB_get_token_family_key (
1815 : TMH_db,
1816 4 : oc->hc->instance->settings.id,
1817 : slug,
1818 : valid_at,
1819 4 : oc->parse_order.order->pay_deadline,
1820 : &key_details);
1821 4 : switch (qs)
1822 : {
1823 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1824 0 : GNUNET_break (0);
1825 0 : reply_with_error (oc,
1826 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1827 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1828 : "get_token_family_key");
1829 0 : return GNUNET_SYSERR;
1830 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1831 : /* Single-statement transaction shouldn't possibly cause serialization errors.
1832 : Thus treating like a hard error. */
1833 0 : GNUNET_break (0);
1834 0 : reply_with_error (oc,
1835 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1836 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
1837 : "get_token_family_key");
1838 0 : return GNUNET_SYSERR;
1839 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1840 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1841 : "Output token family slug %s unknown at %llu for %llu for instance %s\n",
1842 : slug,
1843 : (unsigned long long) valid_at.abs_time.abs_value_us,
1844 : (unsigned long long) oc->parse_order.order->pay_deadline.abs_time.abs_value_us,
1845 : oc->hc->instance->settings.id);
1846 0 : reply_with_error (oc,
1847 : MHD_HTTP_NOT_FOUND,
1848 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
1849 : slug);
1850 0 : return GNUNET_SYSERR;
1851 4 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1852 4 : break;
1853 : }
1854 :
1855 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1856 : "Lookup of token family %s at %llu yielded %s\n",
1857 : slug,
1858 : (unsigned long long) valid_at.abs_time.abs_value_us,
1859 : NULL == key_details.pub.public_key ? "no key" : "a key");
1860 :
1861 : /* add_family_key() must run even if the family already exists, else a
1862 : DB-only key would never reach the in-memory family and the
1863 : find_key_index() assertion below aborts the backend (SIGABRT). */
1864 4 : add_family_key (oc,
1865 : &key_details);
1866 4 : if (NULL == family)
1867 : {
1868 4 : family = find_family (oc,
1869 : slug);
1870 4 : GNUNET_assert (NULL != family);
1871 : }
1872 : /* we don't need the full family details anymore */
1873 4 : GNUNET_free (key_details.token_family.slug);
1874 4 : GNUNET_free (key_details.token_family.name);
1875 4 : GNUNET_free (key_details.token_family.description);
1876 4 : json_decref (key_details.token_family.description_i18n);
1877 4 : json_decref (key_details.token_family.extra_data);
1878 :
1879 4 : if (NULL != key_details.pub.public_key)
1880 : {
1881 : /* get_token_family_key must have found a matching key,
1882 : and it must have been added. Find and use the index. */
1883 0 : GNUNET_CRYPTO_blind_sign_pub_decref (key_details.pub.public_key);
1884 0 : GNUNET_CRYPTO_blind_sign_priv_decref (key_details.priv.private_key);
1885 0 : GNUNET_free (key_details.token_family.cipher_spec);
1886 0 : GNUNET_assert (GNUNET_OK ==
1887 : find_key_index (family,
1888 : valid_at,
1889 : key_index));
1890 0 : return GNUNET_OK;
1891 : }
1892 :
1893 : /* No suitable key exists, create one! */
1894 : {
1895 : struct TALER_MERCHANT_ContractTokenFamilyKey key;
1896 : enum GNUNET_DB_QueryStatus iqs;
1897 : struct TALER_TokenIssuePrivateKey token_priv;
1898 : struct GNUNET_TIME_Timestamp key_expires;
1899 : struct GNUNET_TIME_Timestamp round_start;
1900 :
1901 4 : if (GNUNET_OK !=
1902 4 : get_rounded_time_interval_down (
1903 : key_details.token_family.validity_granularity,
1904 : GNUNET_TIME_absolute_to_timestamp (
1905 : GNUNET_TIME_absolute_subtract (
1906 : valid_at.abs_time,
1907 : key_details.token_family.start_offset)),
1908 : &round_start))
1909 : {
1910 0 : GNUNET_break (0);
1911 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1912 : "Unsupported validity granularity interval %s found in database for token family %s!\n",
1913 : GNUNET_TIME_relative2s (
1914 : key_details.token_family.validity_granularity,
1915 : false),
1916 : slug);
1917 0 : GNUNET_free (key_details.token_family.cipher_spec);
1918 0 : reply_with_error (oc,
1919 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1920 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1921 : "get_rounded_time_interval_down failed");
1922 0 : return GNUNET_SYSERR;
1923 : }
1924 4 : if (GNUNET_TIME_relative_cmp (
1925 : key_details.token_family.duration,
1926 : <,
1927 : GNUNET_TIME_relative_add (
1928 : key_details.token_family.validity_granularity,
1929 : key_details.token_family.start_offset)))
1930 : {
1931 0 : GNUNET_break (0);
1932 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1933 : "Inconsistent duration %s found in database for token family %s (below validity granularity plus start_offset)!\n",
1934 : GNUNET_TIME_relative2s (key_details.token_family.duration,
1935 : false),
1936 : slug);
1937 0 : GNUNET_free (key_details.token_family.cipher_spec);
1938 0 : reply_with_error (oc,
1939 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1940 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1941 : "duration, validity_granularity and start_offset inconsistent for token family");
1942 0 : return GNUNET_SYSERR;
1943 : }
1944 : key.valid_after
1945 4 : = GNUNET_TIME_timestamp_max (
1946 : GNUNET_TIME_absolute_to_timestamp (
1947 : GNUNET_TIME_absolute_subtract (
1948 : round_start.abs_time,
1949 : key_details.token_family.start_offset)),
1950 : key_details.token_family.valid_after);
1951 : key.valid_before
1952 4 : = GNUNET_TIME_timestamp_min (
1953 : GNUNET_TIME_absolute_to_timestamp (
1954 : GNUNET_TIME_absolute_add (
1955 : key.valid_after.abs_time,
1956 : key_details.token_family.duration)),
1957 : key_details.token_family.valid_before);
1958 4 : GNUNET_assert (GNUNET_OK ==
1959 : get_rounded_time_interval_down (
1960 : key_details.token_family.validity_granularity,
1961 : key.valid_before,
1962 : &key_expires));
1963 : /* Make sure key never expires before the payment deadline */
1964 4 : key_expires = GNUNET_TIME_timestamp_max (
1965 4 : oc->parse_order.order->pay_deadline,
1966 : key_expires);
1967 4 : if (GNUNET_TIME_timestamp_cmp (
1968 : key_expires,
1969 : ==,
1970 : round_start))
1971 : {
1972 : /* valid_before does not actually end after the
1973 : next rounded validity period would start;
1974 : determine next rounded validity period
1975 : start point and extend valid_before to cover
1976 : the full validity period */
1977 0 : GNUNET_assert (
1978 : GNUNET_OK ==
1979 : get_rounded_time_interval_up (
1980 : key_details.token_family.validity_granularity,
1981 : key.valid_before,
1982 : &key_expires));
1983 : /* This should basically always end up being key_expires */
1984 0 : key.valid_before = GNUNET_TIME_timestamp_max (key.valid_before,
1985 : key_expires);
1986 : }
1987 4 : if (GNUNET_OK !=
1988 4 : create_key (key_details.token_family.cipher_spec,
1989 : &token_priv,
1990 : &key.pub))
1991 : {
1992 0 : GNUNET_break (0);
1993 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1994 : "Unsupported cipher family %s found in database for token family %s!\n",
1995 : key_details.token_family.cipher_spec,
1996 : slug);
1997 0 : GNUNET_free (key_details.token_family.cipher_spec);
1998 0 : reply_with_error (oc,
1999 : MHD_HTTP_INTERNAL_SERVER_ERROR,
2000 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
2001 : "invalid cipher stored in local database for token family");
2002 0 : return GNUNET_SYSERR;
2003 : }
2004 4 : GNUNET_free (key_details.token_family.cipher_spec);
2005 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2006 : "Storing new key for slug %s of %s\n",
2007 : slug,
2008 : oc->hc->instance->settings.id);
2009 4 : iqs = TALER_MERCHANTDB_insert_token_family_key (TMH_db,
2010 4 : oc->hc->instance->settings.id,
2011 : slug,
2012 : &key.pub,
2013 : &token_priv,
2014 : key_expires,
2015 : key.valid_after,
2016 : key.valid_before);
2017 4 : GNUNET_CRYPTO_blind_sign_priv_decref (token_priv.private_key);
2018 4 : switch (iqs)
2019 : {
2020 0 : case GNUNET_DB_STATUS_HARD_ERROR:
2021 0 : GNUNET_break (0);
2022 0 : reply_with_error (oc,
2023 : MHD_HTTP_INTERNAL_SERVER_ERROR,
2024 : TALER_EC_GENERIC_DB_STORE_FAILED,
2025 : NULL);
2026 0 : return GNUNET_SYSERR;
2027 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
2028 : /* Single-statement transaction shouldn't possibly cause serialization errors.
2029 : Thus treating like a hard error. */
2030 0 : GNUNET_break (0);
2031 0 : reply_with_error (oc,
2032 : MHD_HTTP_INTERNAL_SERVER_ERROR,
2033 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
2034 : NULL);
2035 0 : return GNUNET_SYSERR;
2036 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
2037 0 : GNUNET_break (0);
2038 0 : reply_with_error (oc,
2039 : MHD_HTTP_INTERNAL_SERVER_ERROR,
2040 : TALER_EC_GENERIC_DB_STORE_FAILED,
2041 : NULL);
2042 0 : return GNUNET_SYSERR;
2043 4 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
2044 4 : break;
2045 : }
2046 4 : *key_index = family->keys_len;
2047 4 : GNUNET_array_append (family->keys,
2048 : family->keys_len,
2049 : key);
2050 : }
2051 4 : return GNUNET_OK;
2052 : }
2053 :
2054 :
2055 : /**
2056 : * Build JSON array that represents all of the token families
2057 : * in the contract.
2058 : *
2059 : * @param[in] oc v1-style order context
2060 : * @return JSON array with token families for the contract
2061 : */
2062 : static json_t *
2063 26 : output_token_families (struct OrderContext *oc)
2064 : {
2065 26 : json_t *token_families = json_object ();
2066 :
2067 26 : GNUNET_assert (NULL != token_families);
2068 35 : for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
2069 : {
2070 9 : const struct TALER_MERCHANT_ContractTokenFamily *family
2071 9 : = &oc->parse_choices.token_families[i];
2072 : json_t *jfamily;
2073 :
2074 9 : jfamily = TALER_MERCHANT_json_from_token_family (family);
2075 :
2076 9 : GNUNET_assert (jfamily != NULL);
2077 :
2078 9 : GNUNET_assert (0 ==
2079 : json_object_set_new (token_families,
2080 : family->slug,
2081 : jfamily));
2082 : }
2083 26 : return token_families;
2084 : }
2085 :
2086 :
2087 : /**
2088 : * Build JSON array that represents all of the contract choices
2089 : * in the contract.
2090 : *
2091 : * @param[in] oc v1-style order context
2092 : * @return JSON array with token families for the contract
2093 : */
2094 : static json_t *
2095 26 : output_contract_choices (struct OrderContext *oc)
2096 : {
2097 26 : json_t *choices = json_array ();
2098 :
2099 26 : GNUNET_assert (NULL != choices);
2100 57 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
2101 : {
2102 31 : oc->parse_choices.choices[i].max_fee =
2103 31 : oc->set_max_fee.details.v1.max_fees[i];
2104 31 : GNUNET_assert (0 == json_array_append_new (
2105 : choices,
2106 : TALER_MERCHANT_json_from_contract_choice (
2107 : &oc->parse_choices.choices[i])));
2108 : }
2109 26 : return choices;
2110 : }
2111 :
2112 :
2113 : /**
2114 : * Serialize order into @a oc->serialize_order.contract,
2115 : * ready to be stored in the database. Upon success, continue
2116 : * processing with check_contract().
2117 : *
2118 : * @param[in,out] oc order context
2119 : */
2120 : static void
2121 106 : phase_serialize_order (struct OrderContext *oc)
2122 : {
2123 : json_t *merchant;
2124 :
2125 106 : merchant = TMH_instance_metadata_to_json (oc->hc->instance);
2126 106 : GNUNET_assert (NULL != merchant);
2127 :
2128 106 : oc->serialize_order.contract = GNUNET_JSON_PACK (
2129 : GNUNET_JSON_pack_string (
2130 : "order_id",
2131 : oc->parse_order.order->order_id),
2132 : GNUNET_JSON_pack_object_steal (
2133 : NULL,
2134 : TALER_MERCHANT_base_terms_serialize (oc->parse_order.order->base)),
2135 : GNUNET_JSON_pack_array_incref (
2136 : "products",
2137 : oc->merge_inventory.products),
2138 : GNUNET_JSON_pack_data_auto (
2139 : "h_wire",
2140 : &oc->select_wire_method.wm->h_wire),
2141 : GNUNET_JSON_pack_string (
2142 : "wire_method",
2143 : oc->select_wire_method.wm->wire_method),
2144 : GNUNET_JSON_pack_timestamp (
2145 : "timestamp",
2146 : oc->parse_order.order->timestamp),
2147 : GNUNET_JSON_pack_timestamp (
2148 : "pay_deadline",
2149 : oc->parse_order.order->pay_deadline),
2150 : GNUNET_JSON_pack_timestamp (
2151 : "wire_transfer_deadline",
2152 : oc->parse_order.order->wire_transfer_deadline),
2153 : GNUNET_JSON_pack_string (
2154 : "merchant_base_url",
2155 : oc->parse_order.merchant_base_url),
2156 : GNUNET_JSON_pack_object_steal (
2157 : "merchant",
2158 : merchant),
2159 : GNUNET_JSON_pack_data_auto (
2160 : "merchant_pub",
2161 : &oc->hc->instance->merchant_pub),
2162 : GNUNET_JSON_pack_array_incref (
2163 : "exchanges",
2164 : oc->select_wire_method.exchanges));
2165 :
2166 : {
2167 : json_t *xtra;
2168 :
2169 106 : switch (oc->parse_order.order->base->version)
2170 : {
2171 80 : case TALER_MERCHANT_CONTRACT_VERSION_0:
2172 80 : xtra = GNUNET_JSON_PACK (
2173 : TALER_JSON_pack_amount ("max_fee",
2174 : &oc->set_max_fee.details.v0.max_fee),
2175 : GNUNET_JSON_pack_allow_null (
2176 : TALER_JSON_pack_amount (
2177 : "tip",
2178 : oc->parse_order.order->details.v0.no_tip
2179 : ? NULL
2180 : : &oc->parse_order.order->details.v0.tip)),
2181 : TALER_JSON_pack_amount (
2182 : "amount",
2183 : &oc->parse_order.order->details.v0.brutto));
2184 80 : break;
2185 26 : case TALER_MERCHANT_CONTRACT_VERSION_1:
2186 : {
2187 26 : json_t *token_families = output_token_families (oc);
2188 26 : json_t *choices = output_contract_choices (oc);
2189 :
2190 26 : if ( (NULL == token_families) ||
2191 : (NULL == choices) )
2192 : {
2193 0 : GNUNET_break (0);
2194 0 : return;
2195 : }
2196 26 : xtra = GNUNET_JSON_PACK (
2197 : GNUNET_JSON_pack_array_steal ("choices",
2198 : choices),
2199 : GNUNET_JSON_pack_object_steal ("token_families",
2200 : token_families));
2201 26 : break;
2202 : }
2203 0 : default:
2204 0 : GNUNET_assert (0);
2205 : }
2206 106 : GNUNET_assert (0 ==
2207 : json_object_update (oc->serialize_order.contract,
2208 : xtra));
2209 106 : json_decref (xtra);
2210 : }
2211 :
2212 :
2213 : /* Pack does not work here, because it doesn't set zero-values for timestamps */
2214 106 : GNUNET_assert (0 ==
2215 : json_object_set_new (
2216 : oc->serialize_order.contract,
2217 : "refund_deadline",
2218 : GNUNET_JSON_from_timestamp (
2219 : oc->parse_order.order->refund_deadline)));
2220 : /* auto_refund should only be set if it is not 0 */
2221 106 : if (! GNUNET_TIME_relative_is_zero (
2222 106 : oc->parse_order.order->base->auto_refund))
2223 : {
2224 : /* Pack does not work here, because it sets zero-values for relative times */
2225 1 : GNUNET_assert (0 ==
2226 : json_object_set_new (
2227 : oc->serialize_order.contract,
2228 : "auto_refund",
2229 : GNUNET_JSON_from_time_rel (
2230 : oc->parse_order.order->base->auto_refund)));
2231 : }
2232 :
2233 106 : oc->phase++;
2234 : }
2235 :
2236 :
2237 : /* ***************** ORDER_PHASE_SET_MAX_FEE **************** */
2238 :
2239 :
2240 : /**
2241 : * Set @a max_fee in @a oc based on @a max_stefan_fee value if not overridden
2242 : * by @a client_fee. If neither is set, set the fee to zero using currency
2243 : * from @a brutto.
2244 : *
2245 : * @param[in,out] oc order context
2246 : * @param brutto brutto amount to compute fee for
2247 : * @param client_fee client-given fee override (or invalid)
2248 : * @param max_stefan_fee maximum STEFAN fee of any exchange
2249 : * @param max_fee set to the maximum stefan fee
2250 : */
2251 : static void
2252 111 : compute_fee (struct OrderContext *oc,
2253 : const struct TALER_Amount *brutto,
2254 : const struct TALER_Amount *client_fee,
2255 : const struct TALER_Amount *max_stefan_fee,
2256 : struct TALER_Amount *max_fee)
2257 : {
2258 111 : const struct TALER_MERCHANTDB_InstanceSettings *settings
2259 111 : = &oc->hc->instance->settings;
2260 :
2261 111 : if (GNUNET_OK ==
2262 111 : TALER_amount_is_valid (client_fee))
2263 : {
2264 0 : *max_fee = *client_fee;
2265 0 : return;
2266 : }
2267 111 : if ( (settings->use_stefan) &&
2268 99 : (NULL != max_stefan_fee) &&
2269 : (GNUNET_OK ==
2270 99 : TALER_amount_is_valid (max_stefan_fee)) )
2271 : {
2272 93 : *max_fee = *max_stefan_fee;
2273 93 : return;
2274 : }
2275 18 : GNUNET_assert (
2276 : GNUNET_OK ==
2277 : TALER_amount_set_zero (brutto->currency,
2278 : max_fee));
2279 : }
2280 :
2281 :
2282 : /**
2283 : * Initialize "set_max_fee" in @a oc based on STEFAN value or client
2284 : * preference. Upon success, continue processing in next phase.
2285 : *
2286 : * @param[in,out] oc order context
2287 : */
2288 : static void
2289 106 : phase_set_max_fee (struct OrderContext *oc)
2290 : {
2291 106 : switch (oc->parse_order.order->base->version)
2292 : {
2293 80 : case TALER_MERCHANT_CONTRACT_VERSION_0:
2294 80 : compute_fee (oc,
2295 80 : &oc->parse_order.order->details.v0.brutto,
2296 80 : &oc->parse_order.order->details.v0.max_fee,
2297 80 : &oc->set_exchanges.details.v0.max_stefan_fee,
2298 : &oc->set_max_fee.details.v0.max_fee);
2299 80 : break;
2300 26 : case TALER_MERCHANT_CONTRACT_VERSION_1:
2301 : oc->set_max_fee.details.v1.max_fees
2302 26 : = GNUNET_new_array (oc->parse_choices.choices_len,
2303 : struct TALER_Amount);
2304 57 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
2305 12 : compute_fee (oc,
2306 31 : &oc->parse_choices.choices[i].amount,
2307 31 : &oc->parse_choices.choices[i].max_fee,
2308 31 : NULL != oc->set_exchanges.details.v1.max_stefan_fees
2309 19 : ? &oc->set_exchanges.details.v1.max_stefan_fees[i]
2310 : : NULL,
2311 31 : &oc->set_max_fee.details.v1.max_fees[i]);
2312 26 : break;
2313 0 : default:
2314 0 : GNUNET_break (0);
2315 0 : break;
2316 : }
2317 106 : oc->phase++;
2318 106 : }
2319 :
2320 :
2321 : /* ***************** ORDER_PHASE_SELECT_WIRE_METHOD **************** */
2322 :
2323 : /**
2324 : * Phase to select a wire method that will be acceptable for the order.
2325 : * If none is "perfect" (allows all choices), might jump back to the
2326 : * previous phase to force "/keys" downloads to see if that helps.
2327 : *
2328 : * @param[in,out] oc order context
2329 : */
2330 : static void
2331 92 : phase_select_wire_method (struct OrderContext *oc)
2332 : {
2333 : const struct TALER_Amount *ea;
2334 92 : struct WireMethodCandidate *best = NULL;
2335 92 : unsigned int max_choices = 0;
2336 92 : unsigned int want_choices = 0;
2337 92 : bool zero_amount = false;
2338 :
2339 92 : switch (oc->parse_order.order->base->version)
2340 : {
2341 74 : case TALER_MERCHANT_CONTRACT_VERSION_0:
2342 74 : ea = &oc->parse_order.order->details.v0.brutto;
2343 74 : if (TALER_amount_is_zero (ea))
2344 0 : zero_amount = true;
2345 74 : break;
2346 18 : case TALER_MERCHANT_CONTRACT_VERSION_1:
2347 37 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
2348 : {
2349 19 : ea = &oc->parse_choices.choices[i].amount;
2350 19 : if (TALER_amount_is_zero (ea))
2351 0 : zero_amount = true;
2352 : }
2353 18 : break;
2354 0 : default:
2355 0 : GNUNET_assert (0);
2356 : }
2357 :
2358 92 : for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
2359 185 : NULL != wmc;
2360 93 : wmc = wmc->next)
2361 : {
2362 93 : unsigned int num_choices = 0;
2363 :
2364 93 : switch (oc->parse_order.order->base->version)
2365 : {
2366 75 : case TALER_MERCHANT_CONTRACT_VERSION_0:
2367 75 : want_choices = 1;
2368 75 : ea = &oc->parse_order.order->details.v0.brutto;
2369 150 : if (TALER_amount_is_zero (ea) ||
2370 75 : TALER_amount_set_test_above (&wmc->total_exchange_limits,
2371 : ea))
2372 75 : num_choices++;
2373 75 : break;
2374 18 : case TALER_MERCHANT_CONTRACT_VERSION_1:
2375 18 : want_choices = oc->parse_choices.choices_len;
2376 37 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
2377 : {
2378 19 : ea = &oc->parse_choices.choices[i].amount;
2379 38 : if (TALER_amount_is_zero (ea) ||
2380 19 : TALER_amount_set_test_above (&wmc->total_exchange_limits,
2381 : ea))
2382 19 : num_choices++;
2383 : }
2384 18 : break;
2385 0 : default:
2386 0 : GNUNET_assert (0);
2387 : }
2388 93 : if (num_choices > max_choices)
2389 : {
2390 92 : best = wmc;
2391 92 : max_choices = num_choices;
2392 : }
2393 : }
2394 :
2395 92 : if ( (want_choices > max_choices) &&
2396 0 : (oc->set_exchanges.promising_exchange) &&
2397 0 : (! oc->set_exchanges.forced_reload) )
2398 : {
2399 0 : oc->set_exchanges.exchange_ok = false;
2400 : /* Not all choices in the contract can work with these
2401 : exchanges, try again with forcing /keys download */
2402 0 : for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
2403 0 : NULL != wmc;
2404 0 : wmc = wmc->next)
2405 : {
2406 0 : json_array_clear (wmc->exchanges);
2407 0 : TALER_amount_set_free (&wmc->total_exchange_limits);
2408 : }
2409 0 : oc->phase = ORDER_PHASE_SET_EXCHANGES;
2410 0 : return;
2411 : }
2412 :
2413 92 : if ( (NULL == best) &&
2414 0 : (! zero_amount) &&
2415 0 : (NULL != oc->parse_request.payment_target) )
2416 : {
2417 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2418 : "Cannot create order: lacking suitable exchanges for payment target `%s'\n",
2419 : oc->parse_request.payment_target);
2420 0 : reply_with_error (
2421 : oc,
2422 : MHD_HTTP_CONFLICT,
2423 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
2424 : oc->parse_request.payment_target);
2425 0 : return;
2426 : }
2427 :
2428 92 : if ( (NULL == best) &&
2429 0 : (! zero_amount) )
2430 : {
2431 : enum MHD_Result mret;
2432 :
2433 : /* We actually do not have ANY workable exchange(s) */
2434 0 : mret = TALER_MHD_reply_json_steal (
2435 : oc->connection,
2436 0 : GNUNET_JSON_PACK (
2437 : TALER_JSON_pack_ec (
2438 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS),
2439 : GNUNET_JSON_pack_allow_null (
2440 : GNUNET_JSON_pack_array_incref (
2441 : "exchange_rejections",
2442 : oc->set_exchanges.exchange_rejections))),
2443 : MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS);
2444 0 : finalize_order (oc,
2445 : mret);
2446 0 : return;
2447 : }
2448 :
2449 92 : if (want_choices > max_choices)
2450 : {
2451 : /* Some choices are unpayable */
2452 0 : GNUNET_log (
2453 : GNUNET_ERROR_TYPE_WARNING,
2454 : "Creating order, but some choices do not work with the selected wire method\n");
2455 : }
2456 92 : if ( (0 == json_array_size (best->exchanges)) &&
2457 0 : (oc->add_payment_details.need_exchange) )
2458 : {
2459 : /* We did not find any reasonable exchange */
2460 0 : GNUNET_log (
2461 : GNUNET_ERROR_TYPE_WARNING,
2462 : "Creating order, but only for choices without payment\n");
2463 : }
2464 :
2465 : oc->select_wire_method.wm
2466 92 : = best->wm;
2467 : oc->select_wire_method.exchanges
2468 92 : = json_incref (best->exchanges);
2469 92 : oc->phase++;
2470 : }
2471 :
2472 :
2473 : /* ***************** ORDER_PHASE_SET_EXCHANGES **************** */
2474 :
2475 : /**
2476 : * Exchange `/keys` processing is done, resume handling
2477 : * the order.
2478 : *
2479 : * @param[in,out] oc context to resume
2480 : */
2481 : static void
2482 92 : resume_with_keys (struct OrderContext *oc)
2483 : {
2484 92 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2485 : "Resuming order processing after /keys downloads\n");
2486 92 : GNUNET_assert (GNUNET_YES == oc->suspended);
2487 92 : GNUNET_CONTAINER_DLL_remove (oc_head,
2488 : oc_tail,
2489 : oc);
2490 92 : oc->suspended = GNUNET_NO;
2491 92 : MHD_resume_connection (oc->connection);
2492 92 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
2493 92 : }
2494 :
2495 :
2496 : /**
2497 : * Given a @a brutto amount for exchange with @a keys, set the
2498 : * @a stefan_fee. Note that @a stefan_fee is updated to the maximum
2499 : * of the input and the computed fee.
2500 : *
2501 : * @param[in,out] keys exchange keys
2502 : * @param brutto some brutto amount the client is to pay
2503 : * @param[in,out] stefan_fee set to STEFAN fee to be paid by the merchant
2504 : */
2505 : static void
2506 93 : compute_stefan_fee (const struct TALER_EXCHANGE_Keys *keys,
2507 : const struct TALER_Amount *brutto,
2508 : struct TALER_Amount *stefan_fee)
2509 : {
2510 : struct TALER_Amount net;
2511 :
2512 93 : if (GNUNET_SYSERR !=
2513 93 : TALER_EXCHANGE_keys_stefan_b2n (keys,
2514 : brutto,
2515 : &net))
2516 : {
2517 : struct TALER_Amount fee;
2518 :
2519 93 : TALER_EXCHANGE_keys_stefan_round (keys,
2520 : &net);
2521 93 : if (-1 == TALER_amount_cmp (brutto,
2522 : &net))
2523 : {
2524 : /* brutto < netto! */
2525 : /* => after rounding, there is no real difference */
2526 0 : net = *brutto;
2527 : }
2528 93 : GNUNET_assert (0 <=
2529 : TALER_amount_subtract (&fee,
2530 : brutto,
2531 : &net));
2532 93 : if ( (GNUNET_OK !=
2533 93 : TALER_amount_is_valid (stefan_fee)) ||
2534 0 : (-1 == TALER_amount_cmp (stefan_fee,
2535 : &fee)) )
2536 : {
2537 93 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2538 : "Updated STEFAN-based fee to %s\n",
2539 : TALER_amount2s (&fee));
2540 93 : *stefan_fee = fee;
2541 : }
2542 : }
2543 93 : }
2544 :
2545 :
2546 : /**
2547 : * Update MAX STEFAN fees based on @a keys.
2548 : *
2549 : * @param[in,out] oc order context to update
2550 : * @param keys keys to derive STEFAN fees from
2551 : */
2552 : static void
2553 92 : update_stefan (struct OrderContext *oc,
2554 : const struct TALER_EXCHANGE_Keys *keys)
2555 : {
2556 92 : switch (oc->parse_order.order->base->version)
2557 : {
2558 74 : case TALER_MERCHANT_CONTRACT_VERSION_0:
2559 74 : compute_stefan_fee (keys,
2560 74 : &oc->parse_order.order->details.v0.brutto,
2561 : &oc->set_exchanges.details.v0.max_stefan_fee);
2562 74 : break;
2563 18 : case TALER_MERCHANT_CONTRACT_VERSION_1:
2564 : oc->set_exchanges.details.v1.max_stefan_fees
2565 18 : = GNUNET_new_array (oc->parse_choices.choices_len,
2566 : struct TALER_Amount);
2567 37 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
2568 19 : if (0 == strcasecmp (keys->currency,
2569 19 : oc->parse_choices.choices[i].amount.currency))
2570 19 : compute_stefan_fee (keys,
2571 19 : &oc->parse_choices.choices[i].amount,
2572 19 : &oc->set_exchanges.details.v1.max_stefan_fees[i]);
2573 18 : break;
2574 0 : default:
2575 0 : GNUNET_assert (0);
2576 : }
2577 92 : }
2578 :
2579 :
2580 : /**
2581 : * Check our KYC status at all exchanges as our current limit is
2582 : * too low and we failed to create an order.
2583 : *
2584 : * @param oc order context
2585 : * @param wmc wire method candidate to notify for
2586 : * @param exchange_url exchange to notify about
2587 : */
2588 : static void
2589 0 : notify_kyc_required (const struct OrderContext *oc,
2590 : const struct WireMethodCandidate *wmc,
2591 : const char *exchange_url)
2592 : {
2593 0 : struct GNUNET_DB_EventHeaderP es = {
2594 0 : .size = htons (sizeof (es)),
2595 0 : .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_RULE_TRIGGERED)
2596 : };
2597 : char *hws;
2598 : char *extra;
2599 :
2600 0 : hws = GNUNET_STRINGS_data_to_string_alloc (
2601 0 : &wmc->wm->h_wire,
2602 : sizeof (wmc->wm->h_wire));
2603 :
2604 0 : GNUNET_asprintf (&extra,
2605 : "%s %s",
2606 : hws,
2607 : exchange_url);
2608 0 : TALER_MERCHANTDB_event_notify (TMH_db,
2609 : &es,
2610 : extra,
2611 0 : strlen (extra) + 1);
2612 0 : GNUNET_free (extra);
2613 0 : GNUNET_free (hws);
2614 0 : }
2615 :
2616 :
2617 : /**
2618 : * Add a reason why a particular exchange was rejected to our
2619 : * response data.
2620 : *
2621 : * @param[in,out] oc order context to update
2622 : * @param exchange_url exchange this is about
2623 : * @param ec error code to set for the exchange
2624 : */
2625 : static void
2626 0 : add_rejection (struct OrderContext *oc,
2627 : const char *exchange_url,
2628 : enum TALER_ErrorCode ec)
2629 : {
2630 0 : if (NULL == oc->set_exchanges.exchange_rejections)
2631 : {
2632 0 : oc->set_exchanges.exchange_rejections = json_array ();
2633 0 : GNUNET_assert (NULL != oc->set_exchanges.exchange_rejections);
2634 : }
2635 0 : GNUNET_assert (0 ==
2636 : json_array_append_new (
2637 : oc->set_exchanges.exchange_rejections,
2638 : GNUNET_JSON_PACK (
2639 : GNUNET_JSON_pack_string ("exchange_url",
2640 : exchange_url),
2641 : TALER_JSON_pack_ec (ec))));
2642 0 : }
2643 :
2644 :
2645 : /**
2646 : * Checks the limits that apply for this @a exchange and
2647 : * the @a wmc and if the exchange is acceptable at all, adds it
2648 : * to the list of exchanges for the @a wmc.
2649 : *
2650 : * @param oc context of the order
2651 : * @param exchange internal handle for the exchange
2652 : * @param exchange_url base URL of this exchange
2653 : * @param wmc wire method to evaluate this exchange for
2654 : * @return true if the exchange is acceptable for the contract
2655 : */
2656 : static bool
2657 93 : get_acceptable (struct OrderContext *oc,
2658 : const struct TMH_Exchange *exchange,
2659 : const char *exchange_url,
2660 : struct WireMethodCandidate *wmc)
2661 : {
2662 93 : const struct TALER_Amount *max_needed = NULL;
2663 93 : unsigned int priority = 42; /* make compiler happy */
2664 : json_t *j_exchange;
2665 : enum TMH_ExchangeStatus res;
2666 : struct TALER_Amount max_amount;
2667 :
2668 93 : for (unsigned int i = 0;
2669 93 : i<oc->add_payment_details.num_max_choice_limits;
2670 0 : i++)
2671 : {
2672 93 : const struct TALER_Amount *val
2673 93 : = &oc->add_payment_details.max_choice_limits[i];
2674 :
2675 93 : if (0 == strcasecmp (val->currency,
2676 : TMH_EXCHANGES_get_currency (exchange)))
2677 : {
2678 93 : max_needed = val;
2679 93 : break;
2680 : }
2681 : }
2682 93 : if (NULL == max_needed)
2683 : {
2684 : /* exchange currency not relevant for any of our choices, skip it */
2685 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2686 : "Exchange %s with currency `%s' is not applicable to this order\n",
2687 : exchange_url,
2688 : TMH_EXCHANGES_get_currency (exchange));
2689 0 : add_rejection (oc,
2690 : exchange_url,
2691 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH);
2692 0 : return false;
2693 : }
2694 :
2695 93 : max_amount = *max_needed;
2696 93 : res = TMH_exchange_check_debit (
2697 93 : oc->hc->instance->settings.id,
2698 : exchange,
2699 : wmc->wm,
2700 : &max_amount);
2701 93 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2702 : "Exchange %s evaluated at %d with max %s\n",
2703 : exchange_url,
2704 : res,
2705 : TALER_amount2s (&max_amount));
2706 93 : if (TALER_amount_is_zero (&max_amount))
2707 : {
2708 0 : if (! TALER_amount_is_zero (max_needed))
2709 : {
2710 : /* Trigger re-checking the current deposit limit when
2711 : * paying non-zero amount with zero deposit limit */
2712 0 : notify_kyc_required (oc,
2713 : wmc,
2714 : exchange_url);
2715 : }
2716 : /* If deposit is impossible, we don't list the
2717 : * exchange in the contract terms. */
2718 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2719 : "Exchange %s deposit limit is zero, skipping it\n",
2720 : exchange_url);
2721 0 : add_rejection (oc,
2722 : exchange_url,
2723 : TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LEGALLY_REFUSED);
2724 0 : return false;
2725 : }
2726 93 : switch (res)
2727 : {
2728 93 : case TMH_ES_OK:
2729 : case TMH_ES_RETRY_OK:
2730 93 : priority = 1024; /* high */
2731 93 : oc->set_exchanges.exchange_ok = true;
2732 93 : break;
2733 0 : case TMH_ES_NO_ACC:
2734 0 : if (oc->set_exchanges.forced_reload)
2735 0 : priority = 0; /* fresh negative response */
2736 : else
2737 0 : priority = 512; /* stale negative response */
2738 0 : break;
2739 0 : case TMH_ES_NO_CURR:
2740 0 : if (oc->set_exchanges.forced_reload)
2741 0 : priority = 0; /* fresh negative response */
2742 : else
2743 0 : priority = 512; /* stale negative response */
2744 0 : break;
2745 0 : case TMH_ES_NO_KEYS:
2746 0 : if (oc->set_exchanges.forced_reload)
2747 0 : priority = 256; /* fresh, no accounts yet */
2748 : else
2749 0 : priority = 768; /* stale, no accounts yet */
2750 0 : break;
2751 0 : case TMH_ES_NO_ACC_RETRY_OK:
2752 0 : if (oc->set_exchanges.forced_reload)
2753 : {
2754 0 : priority = 0; /* fresh negative response */
2755 : }
2756 : else
2757 : {
2758 0 : oc->set_exchanges.promising_exchange = true;
2759 0 : priority = 512; /* stale negative response */
2760 : }
2761 0 : break;
2762 0 : case TMH_ES_NO_CURR_RETRY_OK:
2763 0 : if (oc->set_exchanges.forced_reload)
2764 0 : priority = 0; /* fresh negative response */
2765 : else
2766 0 : priority = 512; /* stale negative response */
2767 0 : break;
2768 0 : case TMH_ES_NO_KEYS_RETRY_OK:
2769 0 : if (oc->set_exchanges.forced_reload)
2770 : {
2771 0 : priority = 256; /* fresh, no accounts yet */
2772 : }
2773 : else
2774 : {
2775 0 : oc->set_exchanges.promising_exchange = true;
2776 0 : priority = 768; /* stale, no accounts yet */
2777 : }
2778 0 : break;
2779 : }
2780 93 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2781 : "Exchange %s deposit limit is %s, adding it!\n",
2782 : exchange_url,
2783 : TALER_amount2s (&max_amount));
2784 :
2785 93 : j_exchange = GNUNET_JSON_PACK (
2786 : GNUNET_JSON_pack_string ("url",
2787 : exchange_url),
2788 : GNUNET_JSON_pack_uint64 ("priority",
2789 : priority),
2790 : TALER_JSON_pack_amount ("max_contribution",
2791 : &max_amount),
2792 : GNUNET_JSON_pack_data_auto ("master_pub",
2793 : TMH_EXCHANGES_get_master_pub (exchange)));
2794 93 : GNUNET_assert (NULL != j_exchange);
2795 : /* Add exchange to list of exchanges for this wire method
2796 : candidate */
2797 93 : GNUNET_assert (0 ==
2798 : json_array_append_new (wmc->exchanges,
2799 : j_exchange));
2800 93 : GNUNET_assert (0 <=
2801 : TALER_amount_set_add (&wmc->total_exchange_limits,
2802 : &max_amount,
2803 : max_needed));
2804 93 : return true;
2805 : }
2806 :
2807 :
2808 : /**
2809 : * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
2810 : * operation.
2811 : *
2812 : * @param cls closure with our `struct RekeyExchange *`
2813 : * @param keys the keys of the exchange
2814 : * @param exchange representation of the exchange
2815 : */
2816 : static void
2817 92 : keys_cb (
2818 : void *cls,
2819 : struct TALER_EXCHANGE_Keys *keys,
2820 : struct TMH_Exchange *exchange)
2821 : {
2822 92 : struct RekeyExchange *rx = cls;
2823 92 : struct OrderContext *oc = rx->oc;
2824 92 : const struct TALER_MERCHANTDB_InstanceSettings *settings =
2825 92 : &oc->hc->instance->settings;
2826 92 : bool applicable = false;
2827 :
2828 92 : rx->fo = NULL;
2829 92 : GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
2830 : oc->set_exchanges.pending_reload_tail,
2831 : rx);
2832 92 : if (NULL == keys)
2833 : {
2834 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2835 : "Failed to download %skeys\n",
2836 : rx->url);
2837 0 : oc->set_exchanges.promising_exchange = true;
2838 0 : add_rejection (oc,
2839 0 : rx->url,
2840 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE);
2841 0 : goto cleanup;
2842 : }
2843 92 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2844 : "Got response for %skeys\n",
2845 : rx->url);
2846 :
2847 : /* Evaluate the use of this exchange for each wire method candidate.
2848 : Note that the exchange may have *several* accounts sharing the same
2849 : wire method; as get_acceptable() judges the exchange as a whole (and
2850 : not the individual account), it must be called at most once per wire
2851 : method candidate, or we would list the exchange more than once in the
2852 : contract terms and count its deposit limit more than once. */
2853 92 : for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
2854 185 : NULL != wmc;
2855 93 : wmc = wmc->next)
2856 : {
2857 93 : bool matches = false;
2858 :
2859 93 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2860 : "Order could use wire method `%s'\n",
2861 : wmc->wm->wire_method);
2862 93 : for (unsigned int j = 0; j<keys->accounts_len; j++)
2863 : {
2864 93 : struct TALER_FullPayto full_payto = keys->accounts[j].fpayto_uri;
2865 93 : char *wire_method = TALER_payto_get_method (full_payto.full_payto);
2866 :
2867 93 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2868 : "Exchange `%s' has wire method `%s'\n",
2869 : rx->url,
2870 : wire_method);
2871 93 : matches = (0 == strcmp (wmc->wm->wire_method,
2872 : wire_method));
2873 93 : GNUNET_free (wire_method);
2874 93 : if (matches)
2875 93 : break;
2876 : }
2877 93 : if (matches)
2878 93 : applicable |= get_acceptable (oc,
2879 : exchange,
2880 93 : rx->url,
2881 : wmc);
2882 : }
2883 92 : if ( (! applicable) &&
2884 0 : (! oc->set_exchanges.forced_reload) )
2885 : {
2886 : /* Checks for 'forced_reload' to not log the error *again*
2887 : if we forced a re-load and are encountering the
2888 : applicability error a 2nd time */
2889 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
2890 : "Exchange `%s' %u wire methods are not applicable to this order\n",
2891 : rx->url,
2892 : keys->accounts_len);
2893 0 : add_rejection (oc,
2894 0 : rx->url,
2895 : TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED);
2896 : }
2897 92 : if (applicable &&
2898 92 : settings->use_stefan)
2899 92 : update_stefan (oc,
2900 : keys);
2901 0 : cleanup:
2902 92 : GNUNET_free (rx->url);
2903 92 : GNUNET_free (rx);
2904 92 : if (NULL != oc->set_exchanges.pending_reload_head)
2905 0 : return;
2906 92 : resume_with_keys (oc);
2907 : }
2908 :
2909 :
2910 : /**
2911 : * Force re-downloading of /keys from @a exchange,
2912 : * we currently have no acceptable exchange, so we
2913 : * should try to get one.
2914 : *
2915 : * @param cls closure with our `struct OrderContext`
2916 : * @param url base URL of the exchange
2917 : * @param exchange internal handle for the exchange
2918 : */
2919 : static void
2920 92 : get_exchange_keys (void *cls,
2921 : const char *url,
2922 : const struct TMH_Exchange *exchange)
2923 : {
2924 92 : struct OrderContext *oc = cls;
2925 : struct RekeyExchange *rx;
2926 :
2927 92 : rx = GNUNET_new (struct RekeyExchange);
2928 92 : rx->oc = oc;
2929 92 : rx->url = GNUNET_strdup (url);
2930 92 : GNUNET_CONTAINER_DLL_insert (oc->set_exchanges.pending_reload_head,
2931 : oc->set_exchanges.pending_reload_tail,
2932 : rx);
2933 92 : if (oc->set_exchanges.forced_reload)
2934 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2935 : "Forcing download of %skeys\n",
2936 : url);
2937 184 : rx->fo = TMH_EXCHANGES_keys4exchange (url,
2938 92 : oc->set_exchanges.forced_reload,
2939 : &keys_cb,
2940 : rx);
2941 92 : }
2942 :
2943 :
2944 : /**
2945 : * Task run when we are timing out on /keys and will just
2946 : * proceed with what we got.
2947 : *
2948 : * @param cls our `struct OrderContext *` to resume
2949 : */
2950 : static void
2951 0 : wakeup_timeout (void *cls)
2952 : {
2953 0 : struct OrderContext *oc = cls;
2954 :
2955 0 : oc->set_exchanges.wakeup_task = NULL;
2956 0 : GNUNET_assert (GNUNET_YES == oc->suspended);
2957 0 : GNUNET_CONTAINER_DLL_remove (oc_head,
2958 : oc_tail,
2959 : oc);
2960 0 : MHD_resume_connection (oc->connection);
2961 0 : oc->suspended = GNUNET_NO;
2962 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
2963 0 : }
2964 :
2965 :
2966 : /**
2967 : * Set list of acceptable exchanges in @a oc. Upon success, continues
2968 : * processing with add_payment_details().
2969 : *
2970 : * @param[in,out] oc order context
2971 : * @return true to suspend execution
2972 : */
2973 : static bool
2974 198 : phase_set_exchanges (struct OrderContext *oc)
2975 : {
2976 198 : if (NULL != oc->set_exchanges.wakeup_task)
2977 : {
2978 92 : GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
2979 92 : oc->set_exchanges.wakeup_task = NULL;
2980 : }
2981 :
2982 198 : if (! oc->add_payment_details.need_exchange)
2983 : {
2984 : /* Total amount is zero, so we don't actually need exchanges! */
2985 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2986 : "Order total is zero, no need for exchanges\n");
2987 14 : oc->select_wire_method.exchanges = json_array ();
2988 14 : GNUNET_assert (NULL != oc->select_wire_method.exchanges);
2989 : /* Pick first one, doesn't matter as the amount is zero */
2990 14 : oc->select_wire_method.wm = oc->hc->instance->wm_head;
2991 14 : oc->phase = ORDER_PHASE_SET_MAX_FEE;
2992 14 : return false;
2993 : }
2994 184 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2995 : "Trying to find exchanges\n");
2996 184 : if (NULL == oc->set_exchanges.pending_reload_head)
2997 : {
2998 184 : if (! oc->set_exchanges.exchanges_tried)
2999 : {
3000 92 : oc->set_exchanges.exchanges_tried = true;
3001 : oc->set_exchanges.keys_timeout
3002 92 : = GNUNET_TIME_relative_to_absolute (MAX_KEYS_WAIT);
3003 92 : TMH_exchange_get_trusted (&get_exchange_keys,
3004 : oc);
3005 : }
3006 92 : else if ( (! oc->set_exchanges.forced_reload) &&
3007 92 : (oc->set_exchanges.promising_exchange) &&
3008 0 : (! oc->set_exchanges.exchange_ok) )
3009 : {
3010 0 : for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
3011 0 : NULL != wmc;
3012 0 : wmc = wmc->next)
3013 0 : GNUNET_break (0 ==
3014 : json_array_clear (wmc->exchanges));
3015 : /* Try one more time with forcing /keys download */
3016 0 : oc->set_exchanges.forced_reload = true;
3017 0 : TMH_exchange_get_trusted (&get_exchange_keys,
3018 : oc);
3019 : }
3020 : }
3021 184 : if (GNUNET_TIME_absolute_is_past (oc->set_exchanges.keys_timeout))
3022 : {
3023 : struct RekeyExchange *rx;
3024 :
3025 0 : while (NULL != (rx = oc->set_exchanges.pending_reload_head))
3026 : {
3027 0 : GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
3028 : oc->set_exchanges.pending_reload_tail,
3029 : rx);
3030 0 : TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
3031 0 : GNUNET_free (rx->url);
3032 0 : GNUNET_free (rx);
3033 : }
3034 : }
3035 184 : if (NULL != oc->set_exchanges.pending_reload_head)
3036 : {
3037 92 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
3038 : "Still trying to (re)load %skeys\n",
3039 : oc->set_exchanges.pending_reload_head->url);
3040 : oc->set_exchanges.wakeup_task
3041 92 : = GNUNET_SCHEDULER_add_at (oc->set_exchanges.keys_timeout,
3042 : &wakeup_timeout,
3043 : oc);
3044 92 : MHD_suspend_connection (oc->connection);
3045 92 : oc->suspended = GNUNET_YES;
3046 92 : GNUNET_CONTAINER_DLL_insert (oc_head,
3047 : oc_tail,
3048 : oc);
3049 92 : return true; /* reloads pending */
3050 : }
3051 92 : oc->phase++;
3052 92 : return false;
3053 : }
3054 :
3055 :
3056 : /* ***************** ORDER_PHASE_ADD_PAYMENT_DETAILS **************** */
3057 :
3058 : /**
3059 : * Process the @a payment_target and add the details of how the
3060 : * order could be paid to @a order. On success, continue
3061 : * processing with add_payment_fees().
3062 : *
3063 : * @param[in,out] oc order context
3064 : */
3065 : static void
3066 108 : phase_add_payment_details (struct OrderContext *oc)
3067 : {
3068 : /* First, determine the maximum amounts that could be paid per currency */
3069 108 : switch (oc->parse_order.order->base->version)
3070 : {
3071 82 : case TALER_MERCHANT_CONTRACT_VERSION_0:
3072 82 : GNUNET_array_append (oc->add_payment_details.max_choice_limits,
3073 : oc->add_payment_details.num_max_choice_limits,
3074 : oc->parse_order.order->details.v0.brutto);
3075 82 : if (! TALER_amount_is_zero (
3076 82 : &oc->parse_order.order->details.v0.brutto))
3077 : {
3078 76 : oc->add_payment_details.need_exchange = true;
3079 : }
3080 82 : break;
3081 26 : case TALER_MERCHANT_CONTRACT_VERSION_1:
3082 57 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
3083 : {
3084 31 : const struct TALER_Amount *amount
3085 31 : = &oc->parse_choices.choices[i].amount;
3086 31 : bool found = false;
3087 :
3088 31 : if (! TALER_amount_is_zero (amount))
3089 : {
3090 19 : oc->add_payment_details.need_exchange = true;
3091 : }
3092 31 : for (unsigned int j = 0;
3093 31 : j<oc->add_payment_details.num_max_choice_limits;
3094 0 : j++)
3095 : {
3096 5 : struct TALER_Amount *mx = &oc->add_payment_details.max_choice_limits[j];
3097 5 : if (GNUNET_YES ==
3098 5 : TALER_amount_cmp_currency (mx,
3099 : amount))
3100 : {
3101 5 : TALER_amount_max (mx,
3102 : mx,
3103 : amount);
3104 5 : found = true;
3105 5 : break;
3106 : }
3107 : }
3108 31 : if (! found)
3109 : {
3110 26 : GNUNET_array_append (oc->add_payment_details.max_choice_limits,
3111 : oc->add_payment_details.num_max_choice_limits,
3112 : *amount);
3113 : }
3114 : }
3115 26 : break;
3116 0 : default:
3117 0 : GNUNET_assert (0);
3118 : }
3119 :
3120 : /* Then, create a candidate for each available wire method */
3121 108 : for (struct TMH_WireMethod *wm = oc->hc->instance->wm_head;
3122 223 : NULL != wm;
3123 115 : wm = wm->next)
3124 : {
3125 : struct WireMethodCandidate *wmc;
3126 :
3127 : /* Locate wire method that has a matching payment target */
3128 115 : if (! wm->active)
3129 6 : continue; /* ignore inactive methods */
3130 109 : if ( (NULL != oc->parse_request.payment_target) &&
3131 19 : (0 != strcasecmp (oc->parse_request.payment_target,
3132 19 : wm->wire_method) ) )
3133 2 : continue; /* honor client preference */
3134 107 : wmc = GNUNET_new (struct WireMethodCandidate);
3135 107 : wmc->wm = wm;
3136 107 : wmc->exchanges = json_array ();
3137 107 : GNUNET_assert (NULL != wmc->exchanges);
3138 107 : GNUNET_CONTAINER_DLL_insert (oc->add_payment_details.wmc_head,
3139 : oc->add_payment_details.wmc_tail,
3140 : wmc);
3141 : }
3142 :
3143 108 : if (NULL == oc->add_payment_details.wmc_head)
3144 : {
3145 2 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
3146 : "No wire method available for instance '%s'\n",
3147 : oc->hc->instance->settings.id);
3148 2 : reply_with_error (oc,
3149 : MHD_HTTP_NOT_FOUND,
3150 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
3151 : oc->parse_request.payment_target);
3152 2 : return;
3153 : }
3154 :
3155 : /* next, we'll evaluate available exchanges */
3156 106 : oc->phase++;
3157 : }
3158 :
3159 :
3160 : /* ***************** ORDER_PHASE_MERGE_INVENTORY **************** */
3161 :
3162 :
3163 : /**
3164 : * Helper function to sort uint64_t array with qsort().
3165 : *
3166 : * @param a pointer to element to compare
3167 : * @param b pointer to element to compare
3168 : * @return 0 on equal, -1 on smaller, 1 on larger
3169 : */
3170 : static int
3171 0 : uint64_cmp (const void *a,
3172 : const void *b)
3173 : {
3174 0 : uint64_t ua = *(const uint64_t *) a;
3175 0 : uint64_t ub = *(const uint64_t *) b;
3176 :
3177 0 : if (ua < ub)
3178 0 : return -1;
3179 0 : if (ua > ub)
3180 0 : return 1;
3181 0 : return 0;
3182 : }
3183 :
3184 :
3185 : /**
3186 : * Merge the inventory products into products, querying the
3187 : * database about the details of those products. Upon success,
3188 : * continue processing by calling add_payment_details().
3189 : *
3190 : * @param[in,out] oc order context to process
3191 : */
3192 : static void
3193 113 : phase_merge_inventory (struct OrderContext *oc)
3194 113 : {
3195 113 : uint64_t pots[oc->parse_order.order->products_len + 1];
3196 113 : size_t pots_off = 0;
3197 :
3198 113 : if (0 != oc->parse_order.order->base->default_money_pot)
3199 2 : pots[pots_off++] = oc->parse_order.order->base->default_money_pot;
3200 : /**
3201 : * parse_request.inventory_products => instructions to add products to contract terms
3202 : * parse_order.products => contains products that are not from the backend-managed inventory.
3203 : */
3204 113 : oc->merge_inventory.products = json_array ();
3205 118 : for (size_t i = 0; i<oc->parse_order.order->products_len; i++)
3206 : {
3207 5 : GNUNET_assert (
3208 : 0 ==
3209 : json_array_append_new (
3210 : oc->merge_inventory.products,
3211 : TALER_MERCHANT_product_sold_serialize (
3212 : &oc->parse_order.order->products[i])));
3213 5 : if (0 != oc->parse_order.order->products[i].product_money_pot)
3214 0 : pots[pots_off++] = oc->parse_order.order->products[i].product_money_pot;
3215 : }
3216 :
3217 : /* make sure pots array only has distinct elements */
3218 113 : qsort (pots,
3219 : pots_off,
3220 : sizeof (uint64_t),
3221 : &uint64_cmp);
3222 : {
3223 113 : size_t e = 0;
3224 :
3225 113 : for (size_t i = 1; i<pots_off; i++)
3226 : {
3227 0 : if (pots[e] != pots[i])
3228 0 : pots[++e] = pots[i];
3229 : }
3230 113 : if (pots_off > 0)
3231 2 : e++;
3232 113 : pots_off = e;
3233 : }
3234 113 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
3235 : "Found %u unique money pots in order\n",
3236 : (unsigned int) pots_off);
3237 :
3238 : /* check if all money pots exist; note that we do NOT treat
3239 : the inventory products to this check, as (1) the foreign key
3240 : constraint should ensure this, and (2) if the money pot
3241 : were deleted (concurrently), the value is specified to be
3242 : considered 0 (aka none) and so we can proceed anyway. */
3243 113 : if (pots_off > 0)
3244 : {
3245 : enum GNUNET_DB_QueryStatus qs;
3246 : uint64_t pot_missing;
3247 :
3248 2 : qs = TALER_MERCHANTDB_get_missing_money_pot (TMH_db,
3249 2 : oc->hc->instance->settings.id,
3250 : pots_off,
3251 : pots,
3252 : &pot_missing);
3253 2 : switch (qs)
3254 : {
3255 0 : case GNUNET_DB_STATUS_HARD_ERROR:
3256 : case GNUNET_DB_STATUS_SOFT_ERROR:
3257 0 : GNUNET_break (0);
3258 0 : reply_with_error (oc,
3259 : MHD_HTTP_INTERNAL_SERVER_ERROR,
3260 : TALER_EC_GENERIC_DB_FETCH_FAILED,
3261 : "get_missing_money_pot");
3262 1 : return;
3263 1 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
3264 : /* great, good case! */
3265 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
3266 : "All money pots exist\n");
3267 1 : break;
3268 1 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
3269 : {
3270 : char mstr[32];
3271 :
3272 1 : GNUNET_snprintf (mstr,
3273 : sizeof (mstr),
3274 : "%llu",
3275 : (unsigned long long) pot_missing);
3276 1 : reply_with_error (oc,
3277 : MHD_HTTP_NOT_FOUND,
3278 : TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
3279 : mstr);
3280 1 : return;
3281 : }
3282 : }
3283 : }
3284 :
3285 : /* Populate products from inventory product array and database */
3286 : {
3287 112 : GNUNET_assert (NULL != oc->merge_inventory.products);
3288 139 : for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
3289 : {
3290 31 : struct InventoryProduct *ip
3291 31 : = &oc->parse_request.inventory_products[i];
3292 : struct TALER_MERCHANTDB_ProductDetails pd;
3293 : enum GNUNET_DB_QueryStatus qs;
3294 31 : size_t num_categories = 0;
3295 31 : uint64_t *categories = NULL;
3296 :
3297 31 : qs = TALER_MERCHANTDB_get_product (TMH_db,
3298 31 : oc->hc->instance->settings.id,
3299 : ip->product_id,
3300 : &pd,
3301 : &num_categories,
3302 : &categories);
3303 31 : if (qs <= 0)
3304 : {
3305 2 : enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
3306 2 : unsigned int http_status = 0;
3307 :
3308 2 : switch (qs)
3309 : {
3310 0 : case GNUNET_DB_STATUS_HARD_ERROR:
3311 0 : GNUNET_break (0);
3312 0 : http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
3313 0 : ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
3314 0 : break;
3315 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
3316 0 : GNUNET_break (0);
3317 0 : http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
3318 0 : ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
3319 0 : break;
3320 2 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
3321 2 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
3322 : "Product %s from order unknown\n",
3323 : ip->product_id);
3324 2 : http_status = MHD_HTTP_NOT_FOUND;
3325 2 : ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
3326 2 : break;
3327 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
3328 : /* case listed to make compilers happy */
3329 0 : GNUNET_assert (0);
3330 : }
3331 2 : reply_with_error (oc,
3332 : http_status,
3333 : ec,
3334 : ip->product_id);
3335 4 : return;
3336 : }
3337 29 : GNUNET_free (categories);
3338 29 : oc->parse_order.order->base->minimum_age
3339 29 : = GNUNET_MAX (oc->parse_order.order->base->minimum_age,
3340 : pd.minimum_age);
3341 : {
3342 : const char *eparam;
3343 :
3344 29 : if ( (! ip->quantity_missing) &&
3345 0 : (ip->quantity > (uint64_t) INT64_MAX) )
3346 : {
3347 0 : GNUNET_break_op (0);
3348 0 : reply_with_error (oc,
3349 : MHD_HTTP_BAD_REQUEST,
3350 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
3351 : "quantity");
3352 0 : TALER_MERCHANTDB_product_details_free (&pd);
3353 2 : return;
3354 : }
3355 29 : if (GNUNET_OK !=
3356 29 : TALER_MERCHANT_vk_process_quantity_inputs (
3357 : TALER_MERCHANT_VK_QUANTITY,
3358 29 : pd.allow_fractional_quantity,
3359 29 : ip->quantity_missing,
3360 29 : (int64_t) ip->quantity,
3361 29 : ip->unit_quantity_missing,
3362 : ip->unit_quantity,
3363 : &ip->quantity,
3364 : &ip->quantity_frac,
3365 : &eparam))
3366 : {
3367 2 : GNUNET_break_op (0);
3368 2 : reply_with_error (oc,
3369 : MHD_HTTP_BAD_REQUEST,
3370 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
3371 : eparam);
3372 2 : TALER_MERCHANTDB_product_details_free (&pd);
3373 2 : return;
3374 : }
3375 : }
3376 : {
3377 54 : struct TALER_MERCHANT_ProductSold ps = {
3378 27 : .product_id = (char *) ip->product_id,
3379 27 : .product_name = pd.product_name,
3380 27 : .description = pd.description,
3381 27 : .description_i18n = pd.description_i18n,
3382 27 : .unit_quantity.integer = ip->quantity,
3383 27 : .unit_quantity.fractional = ip->quantity_frac,
3384 27 : .prices_length = pd.price_array_length,
3385 27 : .prices = GNUNET_new_array (pd.price_array_length,
3386 : struct TALER_Amount),
3387 27 : .prices_are_net = pd.price_is_net,
3388 27 : .image = pd.image,
3389 27 : .taxes = pd.taxes,
3390 27 : .delivery_date = oc->parse_order.order->base->delivery_date,
3391 27 : .product_money_pot = pd.money_pot_id,
3392 27 : .unit = pd.unit,
3393 :
3394 : };
3395 : json_t *p;
3396 : char unit_quantity_buf[64];
3397 :
3398 62 : for (size_t j = 0; j<pd.price_array_length; j++)
3399 : {
3400 : struct TALER_Amount atomic_amount;
3401 :
3402 35 : GNUNET_assert (
3403 : GNUNET_OK ==
3404 : TALER_amount_set_zero (pd.price_array[j].currency,
3405 : &atomic_amount));
3406 35 : atomic_amount.fraction = 1;
3407 35 : GNUNET_assert (
3408 : GNUNET_OK ==
3409 : TALER_MERCHANT_amount_multiply_by_quantity (
3410 : &ps.prices[j],
3411 : &pd.price_array[j],
3412 : &ps.unit_quantity,
3413 : TALER_MERCHANT_ROUND_UP,
3414 : &atomic_amount));
3415 : }
3416 :
3417 27 : TALER_MERCHANT_vk_format_fractional_string (
3418 : TALER_MERCHANT_VK_QUANTITY,
3419 : ip->quantity,
3420 : ip->quantity_frac,
3421 : sizeof (unit_quantity_buf),
3422 : unit_quantity_buf);
3423 27 : if (0 != pd.money_pot_id)
3424 0 : pots[pots_off++] = pd.money_pot_id;
3425 27 : p = TALER_MERCHANT_product_sold_serialize (&ps);
3426 27 : GNUNET_assert (NULL != p);
3427 27 : GNUNET_free (ps.prices);
3428 27 : GNUNET_assert (0 ==
3429 : json_array_append_new (oc->merge_inventory.products,
3430 : p));
3431 : }
3432 27 : TALER_MERCHANTDB_product_details_free (&pd);
3433 : }
3434 : }
3435 :
3436 : /* check if final product list is well-formed */
3437 108 : if (! TMH_products_array_valid (oc->merge_inventory.products))
3438 : {
3439 0 : GNUNET_break_op (0);
3440 0 : reply_with_error (oc,
3441 : MHD_HTTP_BAD_REQUEST,
3442 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
3443 : "order:products");
3444 0 : return;
3445 : }
3446 108 : oc->phase++;
3447 : }
3448 :
3449 :
3450 : /* ***************** ORDER_PHASE_PARSE_CHOICES **************** */
3451 :
3452 : /**
3453 : * Callback function that is called for each donau instance.
3454 : * It simply adds the provided donau_url to the json.
3455 : *
3456 : * @param cls closure with our `struct TALER_MERCHANT_ContractOutput *`
3457 : * @param donau_url the URL of the donau instance
3458 : */
3459 : static void
3460 2 : add_donau_url (void *cls,
3461 : const char *donau_url)
3462 : {
3463 2 : struct TALER_MERCHANT_ContractOutput *output = cls;
3464 :
3465 2 : GNUNET_array_append (output->details.donation_receipt.donau_urls,
3466 : output->details.donation_receipt.donau_urls_len,
3467 : GNUNET_strdup (donau_url));
3468 2 : }
3469 :
3470 :
3471 : /**
3472 : * Add the donau output to the contract output.
3473 : *
3474 : * @param oc order context
3475 : * @param output contract output to add donau URLs to
3476 : */
3477 : static bool
3478 2 : add_donau_output (struct OrderContext *oc,
3479 : struct TALER_MERCHANT_ContractOutput *output)
3480 : {
3481 : enum GNUNET_DB_QueryStatus qs;
3482 :
3483 2 : qs = TALER_MERCHANTDB_iterate_donau_instances_filtered (
3484 : TMH_db,
3485 2 : output->details.donation_receipt.amount.currency,
3486 : &add_donau_url,
3487 : output);
3488 2 : if (qs < 0)
3489 : {
3490 0 : GNUNET_break (0);
3491 0 : reply_with_error (oc,
3492 : MHD_HTTP_INTERNAL_SERVER_ERROR,
3493 : TALER_EC_GENERIC_DB_FETCH_FAILED,
3494 : "donau url parsing db call");
3495 0 : for (unsigned int i = 0;
3496 0 : i < output->details.donation_receipt.donau_urls_len;
3497 0 : i++)
3498 0 : GNUNET_free (output->details.donation_receipt.donau_urls[i]);
3499 0 : GNUNET_array_grow (output->details.donation_receipt.donau_urls,
3500 : output->details.donation_receipt.donau_urls_len,
3501 : 0);
3502 0 : return false;
3503 : }
3504 2 : return true;
3505 : }
3506 :
3507 :
3508 : /**
3509 : * Parse contract choices. Upon success, continue
3510 : * processing with merge_inventory().
3511 : *
3512 : * @param[in,out] oc order context
3513 : */
3514 : static void
3515 113 : phase_parse_choices (struct OrderContext *oc)
3516 : {
3517 113 : switch (oc->parse_order.order->base->version)
3518 : {
3519 87 : case TALER_MERCHANT_CONTRACT_VERSION_0:
3520 87 : oc->phase++;
3521 87 : return;
3522 26 : case TALER_MERCHANT_CONTRACT_VERSION_1:
3523 : /* handle below */
3524 26 : break;
3525 0 : default:
3526 0 : GNUNET_assert (0);
3527 : }
3528 :
3529 : /* Convert order choices to contract choices */
3530 26 : GNUNET_array_grow (oc->parse_choices.choices,
3531 : oc->parse_choices.choices_len,
3532 : oc->parse_order.order->details.v1.choices_len);
3533 57 : for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
3534 : {
3535 31 : const struct TALER_MERCHANT_OrderChoice *ochoice
3536 31 : = &oc->parse_order.order->details.v1.choices[i];
3537 31 : struct TALER_MERCHANT_ContractChoice *cchoice
3538 31 : = &oc->parse_choices.choices[i];
3539 : unsigned int off;
3540 :
3541 31 : if (! TMH_test_exchange_configured_for_currency (
3542 31 : ochoice->amount.currency))
3543 : {
3544 0 : GNUNET_break_op (0);
3545 0 : reply_with_error (oc,
3546 : MHD_HTTP_CONFLICT,
3547 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
3548 0 : ochoice->amount.currency);
3549 0 : return;
3550 : }
3551 31 : cchoice->amount = ochoice->amount;
3552 31 : cchoice->tip = ochoice->tip;
3553 31 : cchoice->no_tip = ochoice->no_tip;
3554 31 : if (NULL != ochoice->description)
3555 8 : cchoice->description = GNUNET_strdup (ochoice->description);
3556 31 : if (NULL != ochoice->description_i18n)
3557 0 : cchoice->description_i18n = json_incref (ochoice->description_i18n);
3558 31 : cchoice->max_fee = ochoice->max_fee;
3559 :
3560 : /* convert inputs */
3561 31 : GNUNET_array_grow (cchoice->inputs,
3562 : cchoice->inputs_len,
3563 : ochoice->inputs_len);
3564 31 : off = 0;
3565 36 : for (unsigned int j = 0; j < ochoice->inputs_len; j++)
3566 : {
3567 5 : const struct TALER_MERCHANT_OrderInput *order_input
3568 5 : = &ochoice->inputs[j];
3569 5 : struct TALER_MERCHANT_ContractInput *contract_input
3570 5 : = &cchoice->inputs[off];
3571 :
3572 5 : contract_input->type = order_input->type;
3573 5 : switch (order_input->type)
3574 : {
3575 0 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
3576 0 : GNUNET_assert (0);
3577 : break;
3578 5 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
3579 : /* Ignore inputs tokens with 'count' field set to 0 */
3580 5 : if (0 == order_input->details.token.count)
3581 0 : continue;
3582 : contract_input->details.token.count
3583 5 : = order_input->details.token.count;
3584 : contract_input->details.token.token_family_slug
3585 5 : = order_input->details.token.token_family_slug;
3586 5 : if (GNUNET_OK !=
3587 5 : add_input_token_family (oc,
3588 : contract_input->details.token.token_family_slug))
3589 : {
3590 0 : GNUNET_break_op (0);
3591 0 : return;
3592 : }
3593 5 : off++;
3594 5 : continue;
3595 : } /* switch input type */
3596 0 : GNUNET_assert (0);
3597 : } /* for all inputs */
3598 31 : GNUNET_array_grow (cchoice->inputs,
3599 : cchoice->inputs_len,
3600 : off);
3601 :
3602 : /* convert outputs */
3603 31 : GNUNET_array_grow (cchoice->outputs,
3604 : cchoice->outputs_len,
3605 : ochoice->outputs_len);
3606 31 : off = 0;
3607 41 : for (unsigned int j = 0; j < ochoice->outputs_len; j++)
3608 : {
3609 10 : const struct TALER_MERCHANT_OrderOutput *order_output
3610 10 : = &ochoice->outputs[j];
3611 10 : struct TALER_MERCHANT_ContractOutput *contract_output
3612 10 : = &cchoice->outputs[off];
3613 :
3614 10 : contract_output->type = order_output->type;
3615 10 : switch (order_output->type)
3616 : {
3617 0 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
3618 0 : GNUNET_assert (0);
3619 : break;
3620 2 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
3621 2 : if (order_output->details.donation_receipt.no_amount)
3622 : {
3623 : contract_output->details.donation_receipt.amount
3624 2 : = ochoice->amount;
3625 : }
3626 : else
3627 : {
3628 : contract_output->details.donation_receipt.amount
3629 0 : = order_output->details.donation_receipt.amount;
3630 : }
3631 2 : if (! add_donau_output (oc,
3632 : contract_output))
3633 : {
3634 0 : GNUNET_break (0);
3635 0 : return;
3636 : }
3637 2 : off++;
3638 2 : continue;
3639 8 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
3640 : /* Ignore inputs tokens with 'count' field set to 0 */
3641 8 : if (0 == order_output->details.token.count)
3642 0 : continue;
3643 :
3644 : contract_output->details.token.token_family_slug
3645 8 : = order_output->details.token.token_family_slug;
3646 : contract_output->details.token.count
3647 8 : = order_output->details.token.count;
3648 8 : if (0 == order_output->details.token.valid_at.abs_time.abs_value_us)
3649 : contract_output->details.token.valid_at
3650 8 : = GNUNET_TIME_timestamp_get ();
3651 : else
3652 : contract_output->details.token.valid_at
3653 0 : = order_output->details.token.valid_at;
3654 8 : if (GNUNET_OK !=
3655 8 : add_output_token_family (
3656 : oc,
3657 : contract_output->details.token.token_family_slug,
3658 : contract_output->details.token.valid_at,
3659 : &contract_output->details.token.key_index))
3660 :
3661 : {
3662 : /* note: reply_with_error() was already called */
3663 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
3664 : "Could not handle output token family `%s'\n",
3665 : contract_output->details.token.token_family_slug);
3666 0 : return;
3667 : }
3668 8 : off++;
3669 8 : continue;
3670 : } /* end switch */
3671 0 : GNUNET_assert (0);
3672 : } /* for outputs */
3673 31 : GNUNET_array_grow (cchoice->outputs,
3674 : cchoice->outputs_len,
3675 : off);
3676 : } /* for all choices */
3677 26 : oc->phase++;
3678 : }
3679 :
3680 :
3681 : /* ***************** ORDER_PHASE_PARSE_ORDER **************** */
3682 :
3683 :
3684 : /**
3685 : * Parse the order field of the request. Upon success, continue
3686 : * processing with parse_choices().
3687 : *
3688 : * @param[in,out] oc order context
3689 : */
3690 : static void
3691 121 : phase_parse_order (struct OrderContext *oc)
3692 : {
3693 121 : const struct TALER_MERCHANTDB_InstanceSettings *settings =
3694 121 : &oc->hc->instance->settings;
3695 121 : bool computed_refund_deadline = false;
3696 :
3697 : oc->parse_order.order
3698 121 : = TALER_MERCHANT_order_parse (
3699 : oc->parse_request.order);
3700 121 : if (NULL == oc->parse_order.order)
3701 : {
3702 6 : GNUNET_break_op (0);
3703 6 : reply_with_error (oc,
3704 : MHD_HTTP_BAD_REQUEST,
3705 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
3706 : "order");
3707 6 : return;
3708 : }
3709 :
3710 115 : switch (oc->parse_order.order->base->version)
3711 : {
3712 89 : case TALER_MERCHANT_CONTRACT_VERSION_0:
3713 89 : if (! TMH_test_exchange_configured_for_currency (
3714 89 : oc->parse_order.order->details.v0.brutto.currency))
3715 : {
3716 2 : GNUNET_break_op (0);
3717 2 : reply_with_error (
3718 : oc,
3719 : MHD_HTTP_CONFLICT,
3720 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
3721 2 : oc->parse_order.order->details.v0.brutto.currency);
3722 2 : return;
3723 : }
3724 87 : break;
3725 26 : case TALER_MERCHANT_CONTRACT_VERSION_1:
3726 26 : break;
3727 0 : default:
3728 0 : GNUNET_break_op (0);
3729 0 : reply_with_error (oc,
3730 : MHD_HTTP_BAD_REQUEST,
3731 : TALER_EC_GENERIC_VERSION_MALFORMED,
3732 : "invalid version specified in order, supported are null, '0' or '1'");
3733 0 : return;
3734 : }
3735 :
3736 : /* Add order_id if it doesn't exist. */
3737 113 : if (NULL == oc->parse_order.order->order_id)
3738 : {
3739 : char buf[256];
3740 : time_t timer;
3741 : struct tm *tm_info;
3742 : size_t off;
3743 : uint64_t rand;
3744 : char *last;
3745 :
3746 42 : time (&timer);
3747 42 : tm_info = localtime (&timer);
3748 42 : if (NULL == tm_info)
3749 : {
3750 0 : reply_with_error (
3751 : oc,
3752 : MHD_HTTP_INTERNAL_SERVER_ERROR,
3753 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME,
3754 : NULL);
3755 0 : return;
3756 : }
3757 42 : off = strftime (buf,
3758 : sizeof (buf) - 1,
3759 : "%Y.%j",
3760 : tm_info);
3761 : /* Check for error state of strftime */
3762 42 : GNUNET_assert (0 != off);
3763 42 : buf[off++] = '-';
3764 : /* The encoded suffix is raw identifier entropy, not a bounded number. */
3765 42 : GNUNET_CRYPTO_random_block (&rand,
3766 : sizeof (rand));
3767 42 : last = GNUNET_STRINGS_data_to_string (&rand,
3768 : sizeof (uint64_t),
3769 : &buf[off],
3770 : sizeof (buf) - off);
3771 42 : GNUNET_assert (NULL != last);
3772 42 : *last = '\0';
3773 :
3774 42 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
3775 : "Assigning order ID `%s' server-side\n",
3776 : buf);
3777 42 : oc->parse_order.order->order_id = GNUNET_strdup (buf);
3778 : }
3779 :
3780 : /* Patch fulfillment URL with order_id (implements #6467). */
3781 113 : if (NULL != oc->parse_order.order->base->fulfillment_url)
3782 : {
3783 : const char *pos;
3784 :
3785 77 : pos = strstr (oc->parse_order.order->base->fulfillment_url,
3786 : "${ORDER_ID}");
3787 77 : if (NULL != pos)
3788 : {
3789 : /* replace ${ORDER_ID} with the real order_id */
3790 : char *nurl;
3791 :
3792 : /* We only allow one placeholder */
3793 0 : if (strstr (pos + strlen ("${ORDER_ID}"),
3794 : "${ORDER_ID}"))
3795 : {
3796 0 : GNUNET_break_op (0);
3797 0 : reply_with_error (oc,
3798 : MHD_HTTP_BAD_REQUEST,
3799 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
3800 : "fulfillment_url");
3801 0 : return;
3802 : }
3803 :
3804 0 : GNUNET_asprintf (
3805 : &nurl,
3806 : "%.*s%s%s",
3807 : /* first output URL until ${ORDER_ID} */
3808 0 : (int) (pos - oc->parse_order.order->base->fulfillment_url),
3809 0 : oc->parse_order.order->base->fulfillment_url,
3810 : /* replace ${ORDER_ID} with the right order_id */
3811 0 : oc->parse_order.order->order_id,
3812 : /* append rest of original URL */
3813 : pos + strlen ("${ORDER_ID}"));
3814 0 : oc->parse_order.order->base->fulfillment_url = GNUNET_strdup (nurl);
3815 0 : GNUNET_free (nurl);
3816 : }
3817 : }
3818 :
3819 113 : if ( (GNUNET_TIME_absolute_is_zero (
3820 199 : oc->parse_order.order->pay_deadline.abs_time)) ||
3821 86 : (GNUNET_TIME_absolute_is_never (
3822 86 : oc->parse_order.order->pay_deadline.abs_time)) )
3823 : {
3824 96 : oc->parse_order.order->pay_deadline
3825 96 : = GNUNET_TIME_relative_to_timestamp (
3826 : settings->default_pay_delay);
3827 96 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
3828 : "Pay deadline was zero (or never), setting to %s\n",
3829 : GNUNET_TIME_timestamp2s (
3830 : oc->parse_order.order->pay_deadline));
3831 : }
3832 17 : else if (GNUNET_TIME_absolute_is_past (
3833 17 : oc->parse_order.order->pay_deadline.abs_time))
3834 : {
3835 0 : GNUNET_break_op (0);
3836 0 : reply_with_error (
3837 : oc,
3838 : MHD_HTTP_BAD_REQUEST,
3839 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PAY_DEADLINE_IN_PAST,
3840 : NULL);
3841 0 : return;
3842 : }
3843 113 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
3844 : "Pay deadline is %s\n",
3845 : GNUNET_TIME_timestamp2s (
3846 : oc->parse_order.order->pay_deadline));
3847 :
3848 : /* Check soundness of refund deadline, and that a timestamp
3849 : * is actually present. */
3850 : {
3851 113 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
3852 :
3853 : /* Add timestamp if it doesn't exist (or is zero) */
3854 113 : if (GNUNET_TIME_absolute_is_zero (
3855 113 : oc->parse_order.order->timestamp.abs_time))
3856 : {
3857 113 : oc->parse_order.order->timestamp = now;
3858 : }
3859 :
3860 : /* If no refund_deadline given, set one based on refund_delay. */
3861 113 : if (GNUNET_TIME_absolute_is_never (
3862 113 : oc->parse_order.order->refund_deadline.abs_time))
3863 : {
3864 40 : if (GNUNET_TIME_relative_is_zero (
3865 : oc->parse_request.refund_delay))
3866 : {
3867 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
3868 : "Refund delay is zero, no refunds are possible for this order\n");
3869 12 : oc->parse_order.order->refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
3870 : }
3871 : else
3872 : {
3873 28 : computed_refund_deadline = true;
3874 28 : oc->parse_order.order->refund_deadline
3875 28 : = GNUNET_TIME_absolute_to_timestamp (
3876 : GNUNET_TIME_absolute_add (
3877 28 : oc->parse_order.order->pay_deadline.abs_time,
3878 : oc->parse_request.refund_delay));
3879 : }
3880 : }
3881 :
3882 113 : if ( (! GNUNET_TIME_absolute_is_zero (
3883 113 : oc->parse_order.order->base->delivery_date.abs_time)) &&
3884 0 : (GNUNET_TIME_absolute_is_past (
3885 0 : oc->parse_order.order->base->delivery_date.abs_time)) )
3886 : {
3887 0 : GNUNET_break_op (0);
3888 0 : reply_with_error (
3889 : oc,
3890 : MHD_HTTP_BAD_REQUEST,
3891 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST,
3892 : NULL);
3893 0 : return;
3894 : }
3895 : }
3896 :
3897 113 : if ( (! GNUNET_TIME_absolute_is_zero (
3898 145 : oc->parse_order.order->refund_deadline.abs_time)) &&
3899 32 : (GNUNET_TIME_absolute_is_past (
3900 32 : oc->parse_order.order->refund_deadline.abs_time)) )
3901 : {
3902 0 : GNUNET_break_op (0);
3903 0 : reply_with_error (
3904 : oc,
3905 : MHD_HTTP_BAD_REQUEST,
3906 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_DEADLINE_IN_PAST,
3907 : NULL);
3908 0 : return;
3909 : }
3910 :
3911 113 : if (GNUNET_TIME_absolute_is_never (
3912 113 : oc->parse_order.order->wire_transfer_deadline.abs_time))
3913 : {
3914 : struct GNUNET_TIME_Absolute start;
3915 :
3916 113 : start = GNUNET_TIME_absolute_max (
3917 113 : oc->parse_order.order->refund_deadline.abs_time,
3918 113 : oc->parse_order.order->pay_deadline.abs_time);
3919 113 : oc->parse_order.order->wire_transfer_deadline
3920 113 : = GNUNET_TIME_absolute_to_timestamp (
3921 : GNUNET_TIME_round_up (
3922 : GNUNET_TIME_absolute_add (
3923 : start,
3924 : settings->default_wire_transfer_delay),
3925 113 : settings->default_wire_transfer_rounding_interval));
3926 113 : if (GNUNET_TIME_absolute_is_never (
3927 113 : oc->parse_order.order->wire_transfer_deadline.abs_time))
3928 : {
3929 0 : GNUNET_break_op (0);
3930 0 : reply_with_error (
3931 : oc,
3932 : MHD_HTTP_BAD_REQUEST,
3933 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER,
3934 : "order:wire_transfer_deadline");
3935 0 : return;
3936 : }
3937 : }
3938 0 : else if (computed_refund_deadline)
3939 : {
3940 : /* if we computed the refund_deadline from default settings
3941 : and did have a configured wire_deadline, make sure that
3942 : the refund_deadline is at or below the wire_deadline. */
3943 0 : oc->parse_order.order->refund_deadline
3944 0 : = GNUNET_TIME_timestamp_min (
3945 0 : oc->parse_order.order->refund_deadline,
3946 0 : oc->parse_order.order->wire_transfer_deadline);
3947 : }
3948 113 : if (GNUNET_TIME_timestamp_cmp (
3949 : oc->parse_order.order->wire_transfer_deadline,
3950 : <,
3951 : oc->parse_order.order->refund_deadline))
3952 : {
3953 0 : GNUNET_break_op (0);
3954 0 : reply_with_error (
3955 : oc,
3956 : MHD_HTTP_BAD_REQUEST,
3957 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE,
3958 : "order:wire_transfer_deadline;order:refund_deadline");
3959 0 : return;
3960 : }
3961 :
3962 : {
3963 : char *url;
3964 :
3965 113 : url = make_merchant_base_url (oc->connection,
3966 113 : settings->id);
3967 113 : if (NULL == url)
3968 : {
3969 0 : GNUNET_break_op (0);
3970 0 : reply_with_error (
3971 : oc,
3972 : MHD_HTTP_BAD_REQUEST,
3973 : TALER_EC_GENERIC_PARAMETER_MISSING,
3974 : "order:merchant_base_url");
3975 0 : return;
3976 : }
3977 113 : oc->parse_order.merchant_base_url = url;
3978 : }
3979 :
3980 : // FIXME: move to util during parsing!
3981 113 : if ( (NULL != oc->parse_order.order->base->delivery_location) &&
3982 0 : (! TMH_location_object_valid (oc->parse_order.order->base->delivery_location)) )
3983 : {
3984 0 : GNUNET_break_op (0);
3985 0 : reply_with_error (oc,
3986 : MHD_HTTP_BAD_REQUEST,
3987 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
3988 : "delivery_location");
3989 0 : return;
3990 : }
3991 :
3992 113 : oc->phase++;
3993 : }
3994 :
3995 :
3996 : /* ***************** ORDER_PHASE_PARSE_REQUEST **************** */
3997 :
3998 : /**
3999 : * Parse the client request. Upon success,
4000 : * continue processing by calling parse_order().
4001 : *
4002 : * @param[in,out] oc order context to process
4003 : */
4004 : static void
4005 121 : phase_parse_request (struct OrderContext *oc)
4006 : {
4007 121 : const json_t *ip = NULL;
4008 121 : const json_t *uuid = NULL;
4009 121 : const char *otp_id = NULL;
4010 121 : bool create_token = true; /* default */
4011 : struct GNUNET_JSON_Specification spec[] = {
4012 121 : GNUNET_JSON_spec_json ("order",
4013 : &oc->parse_request.order),
4014 121 : GNUNET_JSON_spec_mark_optional (
4015 : GNUNET_JSON_spec_relative_time ("refund_delay",
4016 : &oc->parse_request.refund_delay),
4017 : NULL),
4018 121 : GNUNET_JSON_spec_mark_optional (
4019 : GNUNET_JSON_spec_string ("payment_target",
4020 : &oc->parse_request.payment_target),
4021 : NULL),
4022 121 : GNUNET_JSON_spec_mark_optional (
4023 : GNUNET_JSON_spec_array_const ("inventory_products",
4024 : &ip),
4025 : NULL),
4026 121 : GNUNET_JSON_spec_mark_optional (
4027 : TALER_JSON_spec_session_id ("session_id",
4028 : &oc->parse_request.session_id),
4029 : NULL),
4030 121 : GNUNET_JSON_spec_mark_optional (
4031 : GNUNET_JSON_spec_array_const ("lock_uuids",
4032 : &uuid),
4033 : NULL),
4034 121 : GNUNET_JSON_spec_mark_optional (
4035 : GNUNET_JSON_spec_bool ("create_token",
4036 : &create_token),
4037 : NULL),
4038 121 : GNUNET_JSON_spec_mark_optional (
4039 : TALER_JSON_spec_slug ("otp_id",
4040 : &otp_id),
4041 : NULL),
4042 121 : GNUNET_JSON_spec_end ()
4043 : };
4044 : enum GNUNET_GenericReturnValue ret;
4045 :
4046 : oc->parse_request.refund_delay
4047 121 : = oc->hc->instance->settings.default_refund_delay;
4048 121 : ret = TALER_MHD_parse_json_data (oc->connection,
4049 121 : oc->hc->request_body,
4050 : spec);
4051 121 : if (GNUNET_OK != ret)
4052 : {
4053 0 : GNUNET_break_op (0);
4054 0 : finalize_order2 (oc,
4055 : ret);
4056 0 : return;
4057 : }
4058 121 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
4059 : "Refund delay is %s\n",
4060 : GNUNET_TIME_relative2s (oc->parse_request.refund_delay,
4061 : false));
4062 121 : TALER_MERCHANTDB_do_expire_locks (TMH_db);
4063 121 : if (NULL != otp_id)
4064 : {
4065 : struct TALER_MERCHANTDB_OtpDeviceDetails td;
4066 : enum GNUNET_DB_QueryStatus qs;
4067 :
4068 2 : memset (&td,
4069 : 0,
4070 : sizeof (td));
4071 2 : qs = TALER_MERCHANTDB_get_otp_device (TMH_db,
4072 2 : oc->hc->instance->settings.id,
4073 : otp_id,
4074 : &td);
4075 2 : switch (qs)
4076 : {
4077 0 : case GNUNET_DB_STATUS_HARD_ERROR:
4078 0 : GNUNET_break (0);
4079 0 : reply_with_error (oc,
4080 : MHD_HTTP_INTERNAL_SERVER_ERROR,
4081 : TALER_EC_GENERIC_DB_FETCH_FAILED,
4082 : "get_otp_device");
4083 0 : return;
4084 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
4085 0 : GNUNET_break (0);
4086 0 : reply_with_error (oc,
4087 : MHD_HTTP_INTERNAL_SERVER_ERROR,
4088 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
4089 : "get_otp_device");
4090 0 : return;
4091 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
4092 0 : reply_with_error (oc,
4093 : MHD_HTTP_NOT_FOUND,
4094 : TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
4095 : otp_id);
4096 0 : return;
4097 2 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
4098 2 : break;
4099 : }
4100 2 : oc->parse_request.pos_key = td.otp_key;
4101 2 : oc->parse_request.pos_algorithm = td.otp_algorithm;
4102 2 : GNUNET_free (td.otp_description);
4103 : }
4104 121 : if (create_token)
4105 : {
4106 112 : GNUNET_CRYPTO_random_block (&oc->parse_request.claim_token,
4107 : sizeof (oc->parse_request.claim_token));
4108 : }
4109 : /* Compute h_post_data (for idempotency check) */
4110 : {
4111 : char *req_body_enc;
4112 :
4113 : /* Dump normalized JSON to string. */
4114 121 : if (NULL == (req_body_enc
4115 121 : = json_dumps (oc->hc->request_body,
4116 : JSON_ENCODE_ANY
4117 : | JSON_COMPACT
4118 : | JSON_SORT_KEYS)))
4119 : {
4120 0 : GNUNET_break (0);
4121 0 : GNUNET_JSON_parse_free (spec);
4122 0 : reply_with_error (oc,
4123 : MHD_HTTP_INTERNAL_SERVER_ERROR,
4124 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
4125 : "request body normalization for hashing");
4126 0 : return;
4127 : }
4128 121 : GNUNET_CRYPTO_hash (req_body_enc,
4129 : strlen (req_body_enc),
4130 : &oc->parse_request.h_post_data.hash);
4131 121 : GNUNET_free (req_body_enc);
4132 : }
4133 :
4134 : /* parse the inventory_products (optionally given) */
4135 121 : if (NULL != ip)
4136 : {
4137 27 : unsigned int ipl = (unsigned int) json_array_size (ip);
4138 :
4139 27 : if ( (json_array_size (ip) != (size_t) ipl) ||
4140 : (ipl > MAX_PRODUCTS) )
4141 : {
4142 0 : GNUNET_break_op (0);
4143 0 : GNUNET_JSON_parse_free (spec);
4144 0 : reply_with_error (oc,
4145 : MHD_HTTP_BAD_REQUEST,
4146 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
4147 : "inventory_products (too many)");
4148 0 : return;
4149 : }
4150 27 : GNUNET_array_grow (oc->parse_request.inventory_products,
4151 : oc->parse_request.inventory_products_length,
4152 : (unsigned int) json_array_size (ip));
4153 58 : for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
4154 : {
4155 31 : struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i];
4156 : const char *error_name;
4157 : unsigned int error_line;
4158 : struct GNUNET_JSON_Specification ispec[] = {
4159 31 : TALER_JSON_spec_slug ("product_id",
4160 : &ipr->product_id),
4161 31 : GNUNET_JSON_spec_mark_optional (
4162 : GNUNET_JSON_spec_uint64 ("quantity",
4163 : &ipr->quantity),
4164 : &ipr->quantity_missing),
4165 31 : GNUNET_JSON_spec_mark_optional (
4166 : GNUNET_JSON_spec_string ("unit_quantity",
4167 : &ipr->unit_quantity),
4168 : &ipr->unit_quantity_missing),
4169 31 : GNUNET_JSON_spec_mark_optional (
4170 : GNUNET_JSON_spec_uint64 ("product_money_pot",
4171 : &ipr->product_money_pot),
4172 : NULL),
4173 31 : GNUNET_JSON_spec_end ()
4174 : };
4175 :
4176 31 : ret = GNUNET_JSON_parse (json_array_get (ip,
4177 : i),
4178 : ispec,
4179 : &error_name,
4180 : &error_line);
4181 31 : if (GNUNET_OK != ret)
4182 : {
4183 0 : GNUNET_break_op (0);
4184 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
4185 : "Product parsing failed at #%u: %s:%u\n",
4186 : i,
4187 : error_name,
4188 : error_line);
4189 0 : reply_with_error (oc,
4190 : MHD_HTTP_BAD_REQUEST,
4191 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
4192 : "inventory_products");
4193 0 : return;
4194 : }
4195 31 : if (ipr->quantity_missing && ipr->unit_quantity_missing)
4196 : {
4197 0 : ipr->quantity = 1;
4198 0 : ipr->quantity_missing = false;
4199 : }
4200 : }
4201 : }
4202 :
4203 : /* parse the lock_uuids (optionally given) */
4204 121 : if (NULL != uuid)
4205 : {
4206 3 : GNUNET_array_grow (oc->parse_request.uuids,
4207 : oc->parse_request.uuids_length,
4208 : json_array_size (uuid));
4209 6 : for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
4210 : {
4211 3 : json_t *ui = json_array_get (uuid,
4212 : i);
4213 :
4214 3 : if (! json_is_string (ui))
4215 : {
4216 0 : GNUNET_break_op (0);
4217 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
4218 : "UUID parsing failed at #%u\n",
4219 : i);
4220 0 : reply_with_error (oc,
4221 : MHD_HTTP_BAD_REQUEST,
4222 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
4223 : "lock_uuids");
4224 0 : return;
4225 : }
4226 3 : TMH_uuid_from_string (json_string_value (ui),
4227 3 : &oc->parse_request.uuids[i]);
4228 : }
4229 : }
4230 121 : oc->phase++;
4231 : }
4232 :
4233 :
4234 : /* ***************** Main handler **************** */
4235 :
4236 :
4237 : enum MHD_Result
4238 213 : TMH_private_post_orders (
4239 : const struct TMH_RequestHandler *rh,
4240 : struct MHD_Connection *connection,
4241 : struct TMH_HandlerContext *hc)
4242 : {
4243 213 : struct OrderContext *oc = hc->ctx;
4244 :
4245 213 : if (NULL == oc)
4246 : {
4247 121 : oc = GNUNET_new (struct OrderContext);
4248 121 : hc->ctx = oc;
4249 121 : hc->cc = &clean_order;
4250 121 : oc->connection = connection;
4251 121 : oc->hc = hc;
4252 : }
4253 : while (1)
4254 : {
4255 2821 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
4256 : "Processing order in phase %d\n",
4257 : oc->phase);
4258 1517 : switch (oc->phase)
4259 : {
4260 121 : case ORDER_PHASE_PARSE_REQUEST:
4261 121 : phase_parse_request (oc);
4262 121 : break;
4263 121 : case ORDER_PHASE_PARSE_ORDER:
4264 121 : phase_parse_order (oc);
4265 121 : break;
4266 113 : case ORDER_PHASE_PARSE_CHOICES:
4267 113 : phase_parse_choices (oc);
4268 113 : break;
4269 113 : case ORDER_PHASE_MERGE_INVENTORY:
4270 113 : phase_merge_inventory (oc);
4271 113 : break;
4272 108 : case ORDER_PHASE_ADD_PAYMENT_DETAILS:
4273 108 : phase_add_payment_details (oc);
4274 108 : break;
4275 198 : case ORDER_PHASE_SET_EXCHANGES:
4276 198 : if (phase_set_exchanges (oc))
4277 92 : return MHD_YES;
4278 106 : break;
4279 92 : case ORDER_PHASE_SELECT_WIRE_METHOD:
4280 92 : phase_select_wire_method (oc);
4281 92 : break;
4282 106 : case ORDER_PHASE_SET_MAX_FEE:
4283 106 : phase_set_max_fee (oc);
4284 106 : break;
4285 106 : case ORDER_PHASE_SERIALIZE_ORDER:
4286 106 : phase_serialize_order (oc);
4287 106 : break;
4288 106 : case ORDER_PHASE_CHECK_CONTRACT:
4289 106 : phase_check_contract (oc);
4290 106 : break;
4291 106 : case ORDER_PHASE_SALT_FORGETTABLE:
4292 106 : phase_salt_forgettable (oc);
4293 106 : break;
4294 106 : case ORDER_PHASE_EXECUTE_ORDER:
4295 106 : phase_execute_order (oc);
4296 106 : break;
4297 121 : case ORDER_PHASE_FINISHED_MHD_YES:
4298 121 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
4299 : "Finished processing order (1)\n");
4300 121 : return MHD_YES;
4301 0 : case ORDER_PHASE_FINISHED_MHD_NO:
4302 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
4303 : "Finished processing order (0)\n");
4304 0 : return MHD_NO;
4305 : }
4306 : }
4307 : }
4308 :
4309 :
4310 : /* end of taler-merchant-httpd_post-private-orders.c */
|