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