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