Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014, 2015, 2016, 2018, 2020, 2021 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 : */
26 : #include "platform.h"
27 : #include <jansson.h>
28 : #include <taler/taler_signatures.h>
29 : #include <taler/taler_json_lib.h>
30 : #include "taler-merchant-httpd_private-post-orders.h"
31 : #include "taler-merchant-httpd_auditors.h"
32 : #include "taler-merchant-httpd_exchanges.h"
33 : #include "taler-merchant-httpd_helper.h"
34 : #include "taler-merchant-httpd_private-get-orders.h"
35 :
36 :
37 : /**
38 : * How often do we retry the simple INSERT database transaction?
39 : */
40 : #define MAX_RETRIES 3
41 :
42 : /**
43 : * What is the label under which we find/place the merchant's
44 : * jurisdiction in the locations list by default?
45 : */
46 : #define STANDARD_LABEL_MERCHANT_JURISDICTION "_mj"
47 :
48 : /**
49 : * What is the label under which we find/place the merchant's
50 : * address in the locations list by default?
51 : */
52 : #define STANDARD_LABEL_MERCHANT_ADDRESS "_ma"
53 :
54 :
55 : /**
56 : * Check that the given JSON array of products is well-formed.
57 : *
58 : * @param products JSON array to check
59 : * @return #GNUNET_OK if all is fine
60 : */
61 : static enum GNUNET_GenericReturnValue
62 0 : check_products (const json_t *products)
63 : {
64 : size_t index;
65 : json_t *value;
66 :
67 0 : if (! json_is_array (products))
68 : {
69 0 : GNUNET_break (0);
70 0 : return GNUNET_SYSERR;
71 : }
72 0 : json_array_foreach (products, index, value) {
73 : const char *description;
74 0 : const char *product_id = NULL;
75 : uint64_t quantity;
76 0 : const char *unit = NULL;
77 : struct TALER_Amount price;
78 0 : const char *image = NULL;
79 0 : json_t *taxes = NULL;
80 : struct GNUNET_TIME_Timestamp delivery_date;
81 : const char *error_name;
82 : unsigned int error_line;
83 : enum GNUNET_GenericReturnValue res;
84 : struct GNUNET_JSON_Specification spec[] = {
85 0 : GNUNET_JSON_spec_mark_optional (
86 : GNUNET_JSON_spec_string ("product_id",
87 : &product_id),
88 : NULL),
89 0 : TALER_JSON_spec_i18n_str ("description",
90 : &description),
91 0 : GNUNET_JSON_spec_mark_optional (
92 : GNUNET_JSON_spec_uint64 ("quantity",
93 : &quantity),
94 : NULL),
95 0 : GNUNET_JSON_spec_mark_optional (
96 : GNUNET_JSON_spec_string ("unit",
97 : &unit),
98 : NULL),
99 0 : GNUNET_JSON_spec_mark_optional (
100 : TALER_JSON_spec_amount ("price",
101 : TMH_currency,
102 : &price),
103 : NULL),
104 0 : GNUNET_JSON_spec_mark_optional (
105 : GNUNET_JSON_spec_string ("image",
106 : &image),
107 : NULL),
108 0 : GNUNET_JSON_spec_mark_optional (
109 : GNUNET_JSON_spec_json ("taxes",
110 : &taxes),
111 : NULL),
112 0 : GNUNET_JSON_spec_mark_optional (
113 : GNUNET_JSON_spec_timestamp ("delivery_date",
114 : &delivery_date),
115 : NULL),
116 0 : GNUNET_JSON_spec_end ()
117 : };
118 :
119 : /* extract fields we need to sign separately */
120 0 : res = GNUNET_JSON_parse (value,
121 : spec,
122 : &error_name,
123 : &error_line);
124 0 : if (GNUNET_OK != res)
125 : {
126 0 : GNUNET_break (0);
127 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
128 : "Product parsing failed at #%u: %s:%u\n",
129 : (unsigned int) index,
130 : error_name,
131 : error_line);
132 0 : return GNUNET_SYSERR;
133 : }
134 0 : if ( (NULL != taxes) &&
135 0 : (! TMH_taxes_array_valid (taxes) ) )
136 : {
137 0 : GNUNET_break (0);
138 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
139 : "Product parsing failed for taxes\n");
140 0 : GNUNET_JSON_parse_free (spec);
141 0 : return GNUNET_SYSERR;
142 : }
143 0 : if ( (NULL != image) &&
144 0 : (! TMH_image_data_url_valid (image) ) )
145 : {
146 0 : GNUNET_break (0);
147 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
148 : "Product parsing failed for image\n");
149 0 : GNUNET_JSON_parse_free (spec);
150 0 : return GNUNET_SYSERR;
151 : }
152 0 : GNUNET_JSON_parse_free (spec);
153 : }
154 0 : return GNUNET_OK;
155 : }
156 :
157 :
158 : /**
159 : * Generate the base URL for the given merchant instance.
160 : *
161 : * @param connection the MHD connection
162 : * @param instance_id the merchant instance ID
163 : * @returns the merchant instance's base URL
164 : */
165 : static char *
166 0 : make_merchant_base_url (struct MHD_Connection *connection,
167 : const char *instance_id)
168 : {
169 : const char *host;
170 : const char *forwarded_host;
171 : const char *uri_path;
172 0 : struct GNUNET_Buffer buf = { 0 };
173 :
174 0 : if (GNUNET_YES == TALER_mhd_is_https (connection))
175 0 : GNUNET_buffer_write_str (&buf, "https://");
176 : else
177 0 : GNUNET_buffer_write_str (&buf, "http://");
178 0 : host = MHD_lookup_connection_value (connection,
179 : MHD_HEADER_KIND,
180 : MHD_HTTP_HEADER_HOST);
181 0 : forwarded_host = MHD_lookup_connection_value (connection,
182 : MHD_HEADER_KIND,
183 : "X-Forwarded-Host");
184 0 : if (NULL != forwarded_host)
185 : {
186 0 : GNUNET_buffer_write_str (&buf,
187 : forwarded_host);
188 : }
189 : else
190 : {
191 0 : GNUNET_assert (NULL != host);
192 0 : GNUNET_buffer_write_str (&buf,
193 : host);
194 : }
195 0 : uri_path = MHD_lookup_connection_value (connection,
196 : MHD_HEADER_KIND,
197 : "X-Forwarded-Prefix");
198 0 : if (NULL != uri_path)
199 0 : GNUNET_buffer_write_path (&buf, uri_path);
200 :
201 0 : if (0 != strcmp (instance_id,
202 : "default"))
203 : {
204 0 : GNUNET_buffer_write_path (&buf,
205 : "/instances/");
206 0 : GNUNET_buffer_write_str (&buf,
207 : instance_id);
208 : }
209 0 : GNUNET_buffer_write_path (&buf,
210 : "");
211 0 : return GNUNET_buffer_reap_str (&buf);
212 : }
213 :
214 :
215 : /**
216 : * Information about a product we are supposed to add to the order
217 : * based on what we know it from our inventory.
218 : */
219 : struct InventoryProduct
220 : {
221 : /**
222 : * Identifier of the product in the inventory.
223 : */
224 : const char *product_id;
225 :
226 : /**
227 : * Number of units of the product to add to the order.
228 : */
229 : uint32_t quantity;
230 : };
231 :
232 :
233 : /**
234 : * Execute the database transaction to setup the order.
235 : *
236 : * @param hc handler context for the request
237 : * @param order_id unique ID for the order
238 : * @param h_post_data hash of the client's POST request, for idempotency checks
239 : * @param pay_deadline until when does the order have to be paid
240 : * @param[in] order order to process (not modified)
241 : * @param claim_token token to use for access control
242 : * @param inventory_products_length length of the @a inventory_products array
243 : * @param inventory_products array of products to add to @a order from our inventory
244 : * @param uuids_length length of the @a uuids array
245 : * @param uuids array of UUIDs used to reserve products from @a inventory_products
246 : * @param[out] out_of_stock_index which product (by offset) is out of stock, UINT_MAX if all were in-stock
247 : * @return transaction status, 0 if @a uuids were insufficient to reserve required inventory
248 : */
249 : static enum GNUNET_DB_QueryStatus
250 0 : execute_transaction (struct TMH_HandlerContext *hc,
251 : const char *order_id,
252 : const struct TALER_MerchantPostDataHashP *h_post_data,
253 : struct GNUNET_TIME_Timestamp pay_deadline,
254 : const json_t *order,
255 : const struct TALER_ClaimTokenP *claim_token,
256 : unsigned int inventory_products_length,
257 : const struct InventoryProduct inventory_products[],
258 : unsigned int uuids_length,
259 : const struct GNUNET_Uuid uuids[],
260 : unsigned int *out_of_stock_index)
261 : {
262 : enum GNUNET_DB_QueryStatus qs;
263 : struct GNUNET_TIME_Timestamp timestamp;
264 : uint64_t order_serial;
265 :
266 0 : if (GNUNET_OK !=
267 0 : TMH_db->start (TMH_db->cls,
268 : "insert_order"))
269 : {
270 0 : GNUNET_break (0);
271 0 : return GNUNET_DB_STATUS_HARD_ERROR;
272 : }
273 : /* Setup order */
274 0 : qs = TMH_db->insert_order (TMH_db->cls,
275 0 : hc->instance->settings.id,
276 : order_id,
277 : h_post_data,
278 : pay_deadline,
279 : claim_token,
280 : order); // called 'contract terms' at database.
281 0 : if (qs <= 0)
282 : {
283 : /* qs == 0: probably instance does not exist (anymore) */
284 0 : TMH_db->rollback (TMH_db->cls);
285 0 : return qs;
286 : }
287 : /* Migrate locks from UUIDs to new order: first release old locks */
288 0 : for (unsigned int i = 0; i<uuids_length; i++)
289 : {
290 0 : qs = TMH_db->unlock_inventory (TMH_db->cls,
291 0 : &uuids[i]);
292 0 : if (qs < 0)
293 : {
294 0 : TMH_db->rollback (TMH_db->cls);
295 0 : return qs;
296 : }
297 : /* qs == 0 is OK here, that just means we did not HAVE any lock under this
298 : UUID */
299 : }
300 : /* Migrate locks from UUIDs to new order: acquire new locks
301 : (note: this can basically ONLY fail on serializability OR
302 : because the UUID locks were insufficient for the desired
303 : quantities). */
304 0 : for (unsigned int i = 0; i<inventory_products_length; i++)
305 : {
306 0 : qs = TMH_db->insert_order_lock (TMH_db->cls,
307 0 : hc->instance->settings.id,
308 : order_id,
309 0 : inventory_products[i].product_id,
310 0 : inventory_products[i].quantity);
311 0 : if (qs < 0)
312 : {
313 0 : TMH_db->rollback (TMH_db->cls);
314 0 : return qs;
315 : }
316 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
317 : {
318 : /* qs == 0: lock acquisition failed due to insufficient stocks */
319 0 : TMH_db->rollback (TMH_db->cls);
320 0 : *out_of_stock_index = i; /* indicate which product is causing the issue */
321 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
322 : }
323 : }
324 0 : *out_of_stock_index = UINT_MAX;
325 :
326 : /* Get the order serial and timestamp for the order we just created to
327 : update long-poll clients. */
328 0 : qs = TMH_db->lookup_order_summary (TMH_db->cls,
329 0 : hc->instance->settings.id,
330 : order_id,
331 : ×tamp,
332 : &order_serial);
333 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
334 : {
335 0 : TMH_db->rollback (TMH_db->cls);
336 0 : return qs;
337 : }
338 0 : TMH_notify_order_change (hc->instance,
339 : TMH_OSF_NONE,
340 : timestamp,
341 : order_serial);
342 : /* finally, commit transaction (note: if it fails, we ALSO re-acquire
343 : the UUID locks, which is exactly what we want) */
344 0 : qs = TMH_db->commit (TMH_db->cls);
345 0 : if (0 > qs)
346 0 : return qs;
347 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* 1 == success! */
348 : }
349 :
350 :
351 : /**
352 : * Transform an order into a proposal and store it in the
353 : * database. Write the resulting proposal or an error message
354 : * of a MHD connection.
355 : *
356 : * @param connection connection to write the result or error to
357 : * @param hc handler context for the request
358 : * @param h_post_data hash of the client's POST request, for idempotency checks
359 : * @param[in,out] order order to process (can be modified)
360 : * @param claim_token token to use for access control
361 : * @param inventory_products_length length of the @a inventory_products array
362 : * @param inventory_products array of products to add to @a order from our inventory
363 : * @param uuids_length length of the @a uuids array
364 : * @param uuids array of UUIDs used to reserve products from @a inventory_products
365 : * @return MHD result code
366 : */
367 : static MHD_RESULT
368 0 : execute_order (struct MHD_Connection *connection,
369 : struct TMH_HandlerContext *hc,
370 : const struct TALER_MerchantPostDataHashP *h_post_data,
371 : json_t *order,
372 : const struct TALER_ClaimTokenP *claim_token,
373 : unsigned int inventory_products_length,
374 : const struct InventoryProduct inventory_products[],
375 : unsigned int uuids_length,
376 : const struct GNUNET_Uuid uuids[])
377 : {
378 0 : const struct TALER_MERCHANTDB_InstanceSettings *settings =
379 0 : &hc->instance->settings;
380 : struct TALER_Amount total;
381 : const char *order_id;
382 : const char *summary;
383 0 : const char *fulfillment_msg = NULL;
384 : json_t *products;
385 : json_t *merchant;
386 0 : json_t *summary_i18n = NULL;
387 0 : json_t *fulfillment_i18n = NULL;
388 : struct GNUNET_TIME_Timestamp timestamp;
389 0 : struct GNUNET_TIME_Timestamp refund_deadline = { 0 };
390 : struct GNUNET_TIME_Timestamp wire_transfer_deadline;
391 : struct GNUNET_TIME_Timestamp pay_deadline;
392 : struct GNUNET_JSON_Specification spec[] = {
393 0 : TALER_JSON_spec_amount ("amount",
394 : TMH_currency,
395 : &total),
396 0 : GNUNET_JSON_spec_string ("order_id",
397 : &order_id),
398 0 : GNUNET_JSON_spec_string ("summary",
399 : &summary),
400 : /**
401 : * The following entries we don't actually need,
402 : * except to check that the order is well-formed */
403 0 : GNUNET_JSON_spec_json ("products",
404 : &products),
405 0 : GNUNET_JSON_spec_json ("merchant",
406 : &merchant),
407 0 : GNUNET_JSON_spec_mark_optional (
408 : GNUNET_JSON_spec_json ("summary_i18n",
409 : &summary_i18n),
410 : NULL),
411 0 : GNUNET_JSON_spec_mark_optional (
412 : GNUNET_JSON_spec_string ("fulfillment_message",
413 : &fulfillment_msg),
414 : NULL),
415 0 : GNUNET_JSON_spec_mark_optional (
416 : GNUNET_JSON_spec_json ("fulfillment_message_i18n",
417 : &fulfillment_i18n),
418 : NULL),
419 0 : GNUNET_JSON_spec_timestamp ("timestamp",
420 : ×tamp),
421 0 : GNUNET_JSON_spec_mark_optional (
422 : GNUNET_JSON_spec_timestamp ("refund_deadline",
423 : &refund_deadline),
424 : NULL),
425 0 : GNUNET_JSON_spec_timestamp ("pay_deadline",
426 : &pay_deadline),
427 0 : GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
428 : &wire_transfer_deadline),
429 0 : GNUNET_JSON_spec_end ()
430 : };
431 : enum GNUNET_DB_QueryStatus qs;
432 : unsigned int out_of_stock_index;
433 :
434 : /* extract fields we need to sign separately */
435 : {
436 : enum GNUNET_GenericReturnValue res;
437 :
438 0 : res = TALER_MHD_parse_json_data (connection,
439 : order,
440 : spec);
441 0 : if (GNUNET_OK != res)
442 : {
443 0 : GNUNET_break_op (0);
444 : return (GNUNET_NO == res)
445 : ? MHD_YES
446 0 : : MHD_NO;
447 : }
448 : }
449 :
450 : /* check product list in contract is well-formed */
451 0 : if (GNUNET_OK != check_products (products))
452 : {
453 0 : GNUNET_JSON_parse_free (spec);
454 0 : return TALER_MHD_reply_with_error (connection,
455 : MHD_HTTP_BAD_REQUEST,
456 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
457 : "order:products");
458 : }
459 :
460 0 : if ( (NULL != fulfillment_i18n) &&
461 0 : (! TALER_JSON_check_i18n (fulfillment_i18n)) )
462 : {
463 0 : GNUNET_JSON_parse_free (spec);
464 0 : return TALER_MHD_reply_with_error (connection,
465 : MHD_HTTP_BAD_REQUEST,
466 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
467 : "order:fulfillment_message_i18n");
468 : }
469 0 : if ( (NULL != summary_i18n) &&
470 0 : (! TALER_JSON_check_i18n (summary_i18n)) )
471 : {
472 0 : GNUNET_JSON_parse_free (spec);
473 0 : return TALER_MHD_reply_with_error (connection,
474 : MHD_HTTP_BAD_REQUEST,
475 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
476 : "order:summary_i18n");
477 : }
478 :
479 : /* Test if we already have an order with this id */
480 : {
481 : struct TALER_ClaimTokenP token;
482 : json_t *contract_terms;
483 : struct TALER_MerchantPostDataHashP orig_post;
484 :
485 0 : TMH_db->preflight (TMH_db->cls);
486 0 : qs = TMH_db->lookup_order (TMH_db->cls,
487 0 : hc->instance->settings.id,
488 : order_id,
489 : &token,
490 : &orig_post,
491 : &contract_terms);
492 : /* If yes, check for idempotency */
493 0 : if (0 > qs)
494 : {
495 0 : GNUNET_break (0);
496 0 : TMH_db->rollback (TMH_db->cls);
497 0 : GNUNET_JSON_parse_free (spec);
498 0 : return TALER_MHD_reply_with_error (connection,
499 : MHD_HTTP_INTERNAL_SERVER_ERROR,
500 : TALER_EC_GENERIC_DB_FETCH_FAILED,
501 : "lookup_order");
502 : }
503 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
504 : {
505 : MHD_RESULT ret;
506 :
507 0 : json_decref (contract_terms);
508 : /* Comparing the contract terms is sufficient because all the other
509 : params get added to it at some point. */
510 0 : if (0 == GNUNET_memcmp (&orig_post,
511 : h_post_data))
512 : {
513 0 : ret = TALER_MHD_REPLY_JSON_PACK (
514 : connection,
515 : MHD_HTTP_OK,
516 : GNUNET_JSON_pack_string ("order_id",
517 : order_id),
518 : GNUNET_JSON_pack_allow_null (
519 : GNUNET_JSON_pack_data_varsize (
520 : "token",
521 : GNUNET_is_zero (&token)
522 : ? NULL
523 : : &token,
524 : sizeof (token))));
525 : }
526 : else
527 : {
528 : /* This request is not idempotent */
529 0 : ret = TALER_MHD_reply_with_error (
530 : connection,
531 : MHD_HTTP_CONFLICT,
532 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
533 : order_id);
534 : }
535 0 : GNUNET_JSON_parse_free (spec);
536 0 : return ret;
537 : }
538 : }
539 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
540 : "Executing database transaction to create order '%s' for instance '%s'\n",
541 : order_id,
542 : settings->id);
543 0 : for (unsigned int i = 0; i<MAX_RETRIES; i++)
544 : {
545 0 : TMH_db->preflight (TMH_db->cls);
546 0 : qs = execute_transaction (hc,
547 : order_id,
548 : h_post_data,
549 : pay_deadline,
550 : order,
551 : claim_token,
552 : inventory_products_length,
553 : inventory_products,
554 : uuids_length,
555 : uuids,
556 : &out_of_stock_index);
557 0 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
558 0 : break;
559 : }
560 0 : if (0 >= qs)
561 : {
562 0 : GNUNET_JSON_parse_free (spec);
563 : /* Special report if retries insufficient */
564 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
565 : {
566 0 : GNUNET_break (0);
567 0 : return TALER_MHD_reply_with_error (connection,
568 : MHD_HTTP_INTERNAL_SERVER_ERROR,
569 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
570 : NULL);
571 : }
572 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
573 : {
574 : /* should be: contract (!) with same order ID
575 : already exists */
576 0 : return TALER_MHD_reply_with_error (
577 : connection,
578 : MHD_HTTP_CONFLICT,
579 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
580 : order_id);
581 : }
582 : /* Other hard transaction error (disk full, etc.) */
583 0 : GNUNET_break (0);
584 0 : return TALER_MHD_reply_with_error (
585 : connection,
586 : MHD_HTTP_INTERNAL_SERVER_ERROR,
587 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
588 : NULL);
589 : }
590 :
591 : /* DB transaction succeeded, check for out-of-stock */
592 0 : if (out_of_stock_index < UINT_MAX)
593 : {
594 : /* We had a product that has insufficient quantities,
595 : generate the details for the response. */
596 : struct TALER_MERCHANTDB_ProductDetails pd;
597 : MHD_RESULT ret;
598 :
599 0 : memset (&pd, 0, sizeof (pd));
600 0 : qs = TMH_db->lookup_product (
601 0 : TMH_db->cls,
602 0 : hc->instance->settings.id,
603 0 : inventory_products[out_of_stock_index].product_id,
604 : &pd);
605 0 : GNUNET_JSON_parse_free (spec);
606 0 : switch (qs)
607 : {
608 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
609 0 : ret = TALER_MHD_REPLY_JSON_PACK (
610 : connection,
611 : MHD_HTTP_GONE,
612 : GNUNET_JSON_pack_string (
613 : "product_id",
614 : inventory_products[out_of_stock_index].product_id),
615 : GNUNET_JSON_pack_uint64 (
616 : "requested_quantity",
617 : inventory_products[out_of_stock_index].quantity),
618 : GNUNET_JSON_pack_uint64 (
619 : "available_quantity",
620 : pd.total_stock - pd.total_sold - pd.total_lost),
621 : GNUNET_JSON_pack_allow_null (
622 : GNUNET_JSON_pack_timestamp (
623 : "restock_expected",
624 : pd.next_restock)));
625 0 : TALER_MERCHANTDB_product_details_free (&pd);
626 0 : return ret;
627 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
628 0 : return TALER_MHD_REPLY_JSON_PACK (
629 : connection,
630 : MHD_HTTP_GONE,
631 : GNUNET_JSON_pack_string (
632 : "product_id",
633 : inventory_products[out_of_stock_index].product_id),
634 : GNUNET_JSON_pack_uint64 (
635 : "requested_quantity",
636 : inventory_products[out_of_stock_index].quantity),
637 : GNUNET_JSON_pack_uint64 (
638 : "available_quantity",
639 : 0));
640 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
641 0 : GNUNET_break (0);
642 0 : return TALER_MHD_reply_with_error (
643 : connection,
644 : MHD_HTTP_INTERNAL_SERVER_ERROR,
645 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
646 : NULL);
647 0 : case GNUNET_DB_STATUS_HARD_ERROR:
648 0 : return TALER_MHD_reply_with_error (
649 : connection,
650 : MHD_HTTP_INTERNAL_SERVER_ERROR,
651 : TALER_EC_GENERIC_DB_FETCH_FAILED,
652 : NULL);
653 : }
654 0 : GNUNET_break (0);
655 0 : return MHD_NO;
656 : }
657 :
658 : /* Everything in-stock, generate positive response */
659 : {
660 : MHD_RESULT ret;
661 :
662 0 : ret = TALER_MHD_REPLY_JSON_PACK (
663 : connection,
664 : MHD_HTTP_OK,
665 : GNUNET_JSON_pack_string ("order_id",
666 : order_id),
667 : GNUNET_JSON_pack_allow_null (
668 : GNUNET_JSON_pack_data_varsize (
669 : "token",
670 : GNUNET_is_zero (claim_token)
671 : ? NULL
672 : : claim_token,
673 : sizeof (*claim_token))));
674 0 : GNUNET_JSON_parse_free (spec);
675 0 : return ret;
676 : }
677 : }
678 :
679 :
680 : /**
681 : * Add missing fields to the order. Upon success, continue
682 : * processing with execute_order().
683 : *
684 : * @param connection connection to write the result or error to
685 : * @param hc handler context for the request
686 : * @param h_post_data hash of the client's POST request, for idempotency checks
687 : * @param[in,out] order order to process (can be modified)
688 : * @param claim_token token to use for access control
689 : * @param refund_delay refund delay
690 : * @param inventory_products_length length of the @a inventory_products array
691 : * @param inventory_products array of products to add to @a order from our inventory
692 : * @param uuids_length length of the @a uuids array
693 : * @param uuids array of UUIDs used to reserve products from @a inventory_products
694 : * @return MHD result code
695 : */
696 : static MHD_RESULT
697 0 : patch_order (struct MHD_Connection *connection,
698 : struct TMH_HandlerContext *hc,
699 : const struct TALER_MerchantPostDataHashP *h_post_data,
700 : json_t *order,
701 : const struct TALER_ClaimTokenP *claim_token,
702 : struct GNUNET_TIME_Relative refund_delay,
703 : unsigned int inventory_products_length,
704 : const struct InventoryProduct inventory_products[],
705 : unsigned int uuids_length,
706 : const struct GNUNET_Uuid uuids[])
707 : {
708 0 : const struct TALER_MERCHANTDB_InstanceSettings *settings =
709 0 : &hc->instance->settings;
710 0 : const char *order_id = NULL;
711 0 : const char *fulfillment_url = NULL;
712 0 : const char *merchant_base_url = NULL;
713 0 : json_t *jmerchant = NULL;
714 0 : json_t *delivery_location = NULL;
715 0 : struct TALER_Amount max_wire_fee = { 0 };
716 0 : struct TALER_Amount max_fee = { 0 };
717 0 : uint32_t wire_fee_amortization = 0;
718 0 : struct GNUNET_TIME_Timestamp timestamp
719 : = GNUNET_TIME_UNIT_ZERO_TS;
720 0 : struct GNUNET_TIME_Timestamp delivery_date
721 : = GNUNET_TIME_UNIT_ZERO_TS;
722 0 : struct GNUNET_TIME_Timestamp refund_deadline
723 : = GNUNET_TIME_UNIT_FOREVER_TS;
724 0 : struct GNUNET_TIME_Timestamp pay_deadline
725 : = GNUNET_TIME_UNIT_ZERO_TS;
726 0 : struct GNUNET_TIME_Timestamp wire_deadline
727 : = GNUNET_TIME_UNIT_FOREVER_TS;
728 : /* auto_refund only needs to be type-checked,
729 : * mostly because in GNUnet relative times can't
730 : * be negative. */
731 : struct GNUNET_TIME_Relative auto_refund;
732 : struct GNUNET_JSON_Specification spec[] = {
733 0 : GNUNET_JSON_spec_mark_optional (
734 : GNUNET_JSON_spec_string ("merchant_base_url",
735 : &merchant_base_url),
736 : NULL),
737 0 : GNUNET_JSON_spec_mark_optional (
738 : GNUNET_JSON_spec_json ("merchant",
739 : &jmerchant),
740 : NULL),
741 0 : GNUNET_JSON_spec_mark_optional (
742 : GNUNET_JSON_spec_string ("order_id",
743 : &order_id),
744 : NULL),
745 0 : GNUNET_JSON_spec_mark_optional (
746 : GNUNET_JSON_spec_string ("fulfillment_url",
747 : &fulfillment_url),
748 : NULL),
749 0 : GNUNET_JSON_spec_mark_optional (
750 : GNUNET_JSON_spec_timestamp ("timestamp",
751 : ×tamp),
752 : NULL),
753 0 : GNUNET_JSON_spec_mark_optional (
754 : GNUNET_JSON_spec_timestamp ("refund_deadline",
755 : &refund_deadline),
756 : NULL),
757 0 : GNUNET_JSON_spec_mark_optional (
758 : GNUNET_JSON_spec_timestamp ("pay_deadline",
759 : &pay_deadline),
760 : NULL),
761 0 : GNUNET_JSON_spec_mark_optional (
762 : GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
763 : &wire_deadline),
764 : NULL),
765 0 : GNUNET_JSON_spec_mark_optional (
766 : TALER_JSON_spec_amount ("max_fee",
767 : TMH_currency,
768 : &max_fee),
769 : NULL),
770 0 : GNUNET_JSON_spec_mark_optional (
771 : TALER_JSON_spec_amount ("max_wire_fee",
772 : TMH_currency,
773 : &max_wire_fee),
774 : NULL),
775 0 : GNUNET_JSON_spec_mark_optional (
776 : GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
777 : &wire_fee_amortization),
778 : NULL),
779 0 : GNUNET_JSON_spec_mark_optional (
780 : GNUNET_JSON_spec_timestamp ("delivery_date",
781 : &delivery_date),
782 : NULL),
783 0 : GNUNET_JSON_spec_mark_optional (
784 : GNUNET_JSON_spec_relative_time ("auto_refund",
785 : &auto_refund),
786 : NULL),
787 0 : GNUNET_JSON_spec_mark_optional (
788 : GNUNET_JSON_spec_json ("delivery_location",
789 : &delivery_location),
790 : NULL),
791 0 : GNUNET_JSON_spec_end ()
792 : };
793 : enum GNUNET_GenericReturnValue ret;
794 :
795 0 : ret = TALER_MHD_parse_json_data (connection,
796 : order,
797 : spec);
798 0 : if (GNUNET_OK != ret)
799 : {
800 0 : GNUNET_break_op (0);
801 : return (GNUNET_NO == ret)
802 : ? MHD_YES
803 0 : : MHD_NO;
804 : }
805 :
806 : /* Add order_id if it doesn't exist. */
807 0 : if (NULL == order_id)
808 : {
809 : char buf[256];
810 : time_t timer;
811 : struct tm *tm_info;
812 : size_t off;
813 : uint64_t rand;
814 : char *last;
815 : json_t *jbuf;
816 :
817 0 : time (&timer);
818 0 : tm_info = localtime (&timer);
819 0 : if (NULL == tm_info)
820 : {
821 0 : GNUNET_JSON_parse_free (spec);
822 0 : return TALER_MHD_reply_with_error (
823 : connection,
824 : MHD_HTTP_INTERNAL_SERVER_ERROR,
825 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME,
826 : NULL);
827 : }
828 0 : off = strftime (buf,
829 : sizeof (buf) - 1,
830 : "%Y.%j",
831 : tm_info);
832 : /* Check for error state of strftime */
833 0 : GNUNET_assert (0 != off);
834 0 : buf[off++] = '-';
835 0 : rand = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
836 : UINT64_MAX);
837 0 : last = GNUNET_STRINGS_data_to_string (&rand,
838 : sizeof (uint64_t),
839 : &buf[off],
840 : sizeof (buf) - off);
841 0 : GNUNET_assert (NULL != last);
842 0 : *last = '\0';
843 0 : jbuf = json_string (buf);
844 0 : GNUNET_assert (NULL != jbuf);
845 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
846 : "Assigning order ID `%s' server-side\n",
847 : buf);
848 0 : GNUNET_break (0 ==
849 : json_object_set_new (order,
850 : "order_id",
851 : jbuf));
852 0 : order_id = json_string_value (jbuf);
853 0 : GNUNET_assert (NULL != order_id);
854 : }
855 :
856 : /* Patch fulfillment URL with order_id (implements #6467). */
857 0 : if (NULL != fulfillment_url)
858 : {
859 : const char *pos;
860 :
861 0 : pos = strstr (fulfillment_url,
862 : "${ORDER_ID}");
863 0 : if (NULL != pos)
864 : {
865 : /* replace ${ORDER_ID} with the real order_id */
866 : char *nurl;
867 :
868 : /* We only allow one placeholder */
869 0 : if (strstr (pos + strlen ("${ORDER_ID}"),
870 : "${ORDER_ID}"))
871 : {
872 0 : GNUNET_break_op (0);
873 0 : GNUNET_JSON_parse_free (spec);
874 0 : return TALER_MHD_reply_with_error (connection,
875 : MHD_HTTP_BAD_REQUEST,
876 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
877 : "fulfillment_url");
878 : }
879 :
880 0 : GNUNET_asprintf (&nurl,
881 : "%.*s%s%s",
882 : /* first output URL until ${ORDER_ID} */
883 0 : (int) (pos - fulfillment_url),
884 : fulfillment_url,
885 : /* replace ${ORDER_ID} with the right order_id */
886 : order_id,
887 : /* append rest of original URL */
888 : pos + strlen ("${ORDER_ID}"));
889 : /* replace in JSON of the order */
890 0 : GNUNET_break (0 ==
891 : json_object_set_new (order,
892 : "fulfillment_url",
893 : json_string (nurl)));
894 0 : GNUNET_free (nurl);
895 : }
896 : }
897 :
898 : /* Check soundness of refund deadline, and that a timestamp
899 : * is actually present. */
900 : {
901 0 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
902 :
903 : /* Add timestamp if it doesn't exist (or is zero) */
904 0 : if (GNUNET_TIME_absolute_is_zero (timestamp.abs_time))
905 : {
906 0 : GNUNET_assert (0 ==
907 : json_object_set_new (order,
908 : "timestamp",
909 : GNUNET_JSON_from_timestamp (now)));
910 : }
911 :
912 : /* If no refund_deadline given, set one based on refund_delay. */
913 0 : if (GNUNET_TIME_absolute_is_never (refund_deadline.abs_time))
914 : {
915 0 : if (GNUNET_TIME_relative_is_zero (refund_delay))
916 : {
917 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
918 : "Refund delay is zero, no refunds are possible for this order\n");
919 0 : refund_deadline = now; /* if delay was 0, ensure that refund_deadline == timestamp */
920 : }
921 : else
922 : {
923 0 : refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_delay);
924 : }
925 :
926 0 : GNUNET_assert (0 ==
927 : json_object_set_new (order,
928 : "refund_deadline",
929 : GNUNET_JSON_from_timestamp (
930 : refund_deadline)));
931 : }
932 0 : if ( (! GNUNET_TIME_absolute_is_zero (delivery_date.abs_time)) &&
933 0 : (GNUNET_TIME_timestamp_cmp (delivery_date,
934 : <,
935 : now)) )
936 : {
937 0 : GNUNET_break_op (0);
938 0 : GNUNET_JSON_parse_free (spec);
939 0 : return TALER_MHD_reply_with_error (
940 : connection,
941 : MHD_HTTP_BAD_REQUEST,
942 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST,
943 : NULL);
944 : }
945 : }
946 :
947 0 : if (GNUNET_TIME_absolute_is_zero (pay_deadline.abs_time))
948 : {
949 : struct GNUNET_TIME_Timestamp t;
950 :
951 0 : t = GNUNET_TIME_relative_to_timestamp (settings->default_pay_delay);
952 0 : GNUNET_assert (0 ==
953 : json_object_set_new (order,
954 : "pay_deadline",
955 : GNUNET_JSON_from_timestamp (t)));
956 : }
957 :
958 0 : if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
959 : {
960 : struct GNUNET_TIME_Timestamp t;
961 :
962 0 : t = GNUNET_TIME_relative_to_timestamp (
963 : GNUNET_TIME_relative_max (settings->default_wire_transfer_delay,
964 : refund_delay));
965 0 : wire_deadline = GNUNET_TIME_timestamp_max (refund_deadline,
966 : t);
967 0 : if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
968 : {
969 0 : GNUNET_break_op (0);
970 0 : GNUNET_JSON_parse_free (spec);
971 0 : return TALER_MHD_reply_with_error (
972 : connection,
973 : MHD_HTTP_BAD_REQUEST,
974 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER,
975 : "order:wire_transfer_deadline");
976 :
977 : }
978 0 : GNUNET_assert (0 ==
979 : json_object_set_new (order,
980 : "wire_transfer_deadline",
981 : GNUNET_JSON_from_timestamp (
982 : wire_deadline)));
983 : }
984 0 : if (GNUNET_TIME_timestamp_cmp (wire_deadline,
985 : <,
986 : refund_deadline))
987 : {
988 0 : GNUNET_break_op (0);
989 0 : GNUNET_JSON_parse_free (spec);
990 0 : return TALER_MHD_reply_with_error (
991 : connection,
992 : MHD_HTTP_BAD_REQUEST,
993 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE,
994 : "order:wire_transfer_deadline;order:refund_deadline");
995 : }
996 :
997 : /* Note: total amount currency match checked
998 : later in execute_order() */
999 0 : if (GNUNET_OK !=
1000 0 : TALER_amount_is_valid (&max_wire_fee))
1001 : {
1002 0 : GNUNET_assert (0 ==
1003 : json_object_set_new (
1004 : order,
1005 : "max_wire_fee",
1006 : TALER_JSON_from_amount (&settings->default_max_wire_fee)));
1007 : }
1008 :
1009 0 : if (GNUNET_OK !=
1010 0 : TALER_amount_is_valid (&max_fee))
1011 : {
1012 0 : GNUNET_assert (0 ==
1013 : json_object_set_new (
1014 : order,
1015 : "max_fee",
1016 : TALER_JSON_from_amount (
1017 : &settings->default_max_deposit_fee)));
1018 : }
1019 0 : if (0 == wire_fee_amortization)
1020 : {
1021 0 : GNUNET_assert (0 ==
1022 : json_object_set_new (
1023 : order,
1024 : "wire_fee_amortization",
1025 : json_integer (
1026 : (json_int_t) settings->default_wire_fee_amortization)));
1027 : }
1028 0 : if (NULL == merchant_base_url)
1029 : {
1030 : char *url;
1031 :
1032 0 : url = make_merchant_base_url (connection,
1033 0 : settings->id);
1034 0 : GNUNET_assert (0 ==
1035 : json_object_set_new (order,
1036 : "merchant_base_url",
1037 : json_string (url)));
1038 0 : GNUNET_free (url);
1039 : }
1040 0 : else if (('\0' == *merchant_base_url) ||
1041 0 : ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
1042 : {
1043 0 : GNUNET_break_op (0);
1044 0 : GNUNET_JSON_parse_free (spec);
1045 0 : return TALER_MHD_reply_with_error (
1046 : connection,
1047 : MHD_HTTP_BAD_REQUEST,
1048 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
1049 : "merchant_base_url is not valid");
1050 : }
1051 :
1052 : /* Fill in merchant information if necessary */
1053 0 : if (NULL != jmerchant)
1054 : {
1055 0 : GNUNET_break_op (0);
1056 0 : GNUNET_JSON_parse_free (spec);
1057 0 : return TALER_MHD_reply_with_error (
1058 : connection,
1059 : MHD_HTTP_BAD_REQUEST,
1060 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
1061 : "'merchant' field already set, but must be provided by backend");
1062 : }
1063 :
1064 : {
1065 : json_t *jm;
1066 :
1067 0 : jm = GNUNET_JSON_PACK (
1068 : GNUNET_JSON_pack_string ("name",
1069 : settings->name),
1070 : GNUNET_JSON_pack_allow_null (
1071 : GNUNET_JSON_pack_string ("website",
1072 : settings->website)),
1073 : GNUNET_JSON_pack_allow_null (
1074 : GNUNET_JSON_pack_string ("email",
1075 : settings->email)),
1076 : GNUNET_JSON_pack_allow_null (
1077 : GNUNET_JSON_pack_string ("logo",
1078 : settings->logo)));
1079 0 : GNUNET_assert (NULL != jm);
1080 : {
1081 : json_t *loca;
1082 :
1083 : /* Handle merchant address */
1084 0 : loca = settings->address;
1085 0 : if (NULL != loca)
1086 : {
1087 0 : loca = json_deep_copy (loca);
1088 0 : GNUNET_assert (0 ==
1089 : json_object_set_new (jm,
1090 : "address",
1091 : loca));
1092 : }
1093 : }
1094 : {
1095 : json_t *locj;
1096 :
1097 : /* Handle merchant jurisdiction */
1098 0 : locj = settings->jurisdiction;
1099 0 : if (NULL != locj)
1100 : {
1101 0 : locj = json_deep_copy (locj);
1102 0 : GNUNET_assert (0 ==
1103 : json_object_set_new (jm,
1104 : "jurisdiction",
1105 : locj));
1106 : }
1107 : }
1108 0 : GNUNET_assert (0 ==
1109 : json_object_set_new (order,
1110 : "merchant",
1111 : jm));
1112 : }
1113 :
1114 : /* add fields to the contract that the backend should provide */
1115 0 : GNUNET_assert (0 ==
1116 : json_object_set (order,
1117 : "exchanges",
1118 : TMH_trusted_exchanges));
1119 0 : GNUNET_assert (0 ==
1120 : json_object_set (order,
1121 : "auditors",
1122 : j_auditors));
1123 0 : GNUNET_assert (0 ==
1124 : json_object_set_new (order,
1125 : "merchant_pub",
1126 : GNUNET_JSON_from_data_auto (
1127 : &hc->instance->merchant_pub)));
1128 0 : if (GNUNET_OK !=
1129 0 : TALER_JSON_contract_seed_forgettable (order))
1130 : {
1131 0 : GNUNET_JSON_parse_free (spec);
1132 0 : return TALER_MHD_reply_with_error (
1133 : connection,
1134 : MHD_HTTP_BAD_REQUEST,
1135 : TALER_EC_GENERIC_JSON_INVALID,
1136 : "could not compute hash of order due to bogus forgettable fields");
1137 : }
1138 :
1139 0 : if ( (NULL != delivery_location) &&
1140 0 : (! TMH_location_object_valid (delivery_location)) )
1141 : {
1142 0 : GNUNET_break_op (0);
1143 0 : GNUNET_JSON_parse_free (spec);
1144 0 : return TALER_MHD_reply_with_error (connection,
1145 : MHD_HTTP_BAD_REQUEST,
1146 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1147 : "delivery_location");
1148 : }
1149 :
1150 : /* sanity check result */
1151 : {
1152 : struct TALER_PrivateContractHashP h_control;
1153 :
1154 0 : switch (TALER_JSON_contract_hash (order,
1155 : &h_control))
1156 : {
1157 0 : case GNUNET_SYSERR:
1158 0 : GNUNET_break (0);
1159 0 : GNUNET_JSON_parse_free (spec);
1160 0 : return TALER_MHD_reply_with_error (
1161 : connection,
1162 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1163 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1164 : "could not compute hash of patched order");
1165 0 : case GNUNET_NO:
1166 0 : GNUNET_JSON_parse_free (spec);
1167 0 : GNUNET_break_op (0);
1168 0 : return TALER_MHD_reply_with_error (
1169 : connection,
1170 : MHD_HTTP_BAD_REQUEST,
1171 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1172 : "order contained unallowed values");
1173 0 : case GNUNET_OK:
1174 0 : break;
1175 : }
1176 0 : }
1177 : {
1178 : MHD_RESULT mres;
1179 :
1180 0 : mres = execute_order (connection,
1181 : hc,
1182 : h_post_data,
1183 : order,
1184 : claim_token,
1185 : inventory_products_length,
1186 : inventory_products,
1187 : uuids_length,
1188 : uuids);
1189 0 : GNUNET_JSON_parse_free (spec);
1190 0 : return mres;
1191 : }
1192 : }
1193 :
1194 :
1195 : /**
1196 : * Process the @a payment_target and add the details of how the
1197 : * order could be paid to @a order. On success, continue
1198 : * processing with patch_order().
1199 : *
1200 : * @param connection connection to write the result or error to
1201 : * @param hc handler context for the request
1202 : * @param h_post_data hash of the client's POST request, for idempotency checks
1203 : * @param[in,out] order order to process (can be modified)
1204 : * @param claim_token token to use for access control
1205 : * @param refund_delay refund delay
1206 : * @param payment_target desired wire method, NULL for no preference
1207 : * @param inventory_products_length length of the @a inventory_products array
1208 : * @param inventory_products array of products to add to @a order from our inventory
1209 : * @param uuids_length length of the @a uuids array
1210 : * @param uuids array of UUIDs used to reserve products from @a inventory_products
1211 : * @return MHD result code
1212 : */
1213 : static MHD_RESULT
1214 0 : add_payment_details (struct MHD_Connection *connection,
1215 : struct TMH_HandlerContext *hc,
1216 : const struct TALER_MerchantPostDataHashP *h_post_data,
1217 : json_t *order,
1218 : const struct TALER_ClaimTokenP *claim_token,
1219 : struct GNUNET_TIME_Relative refund_delay,
1220 : const char *payment_target,
1221 : unsigned int inventory_products_length,
1222 : const struct InventoryProduct inventory_products[],
1223 : unsigned int uuids_length,
1224 : const struct GNUNET_Uuid uuids[])
1225 : {
1226 : struct TMH_WireMethod *wm;
1227 :
1228 0 : wm = hc->instance->wm_head;
1229 : /* Locate wire method that has a matching payment target */
1230 0 : while ( (NULL != wm) &&
1231 0 : ( (! wm->active) ||
1232 0 : ( (NULL != payment_target) &&
1233 0 : (0 != strcasecmp (payment_target,
1234 0 : wm->wire_method) ) ) ) )
1235 0 : wm = wm->next;
1236 0 : if (NULL == wm)
1237 : {
1238 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1239 : "No wire method available for instance '%s'\n",
1240 : hc->instance->settings.id);
1241 0 : return TALER_MHD_reply_with_error (connection,
1242 : MHD_HTTP_NOT_FOUND,
1243 : TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
1244 : payment_target);
1245 : }
1246 0 : GNUNET_assert (0 ==
1247 : json_object_set_new (order,
1248 : "h_wire",
1249 : GNUNET_JSON_from_data_auto (
1250 : &wm->h_wire)));
1251 0 : GNUNET_assert (0 ==
1252 : json_object_set_new (order,
1253 : "wire_method",
1254 : json_string (wm->wire_method)));
1255 0 : return patch_order (connection,
1256 : hc,
1257 : h_post_data,
1258 : order,
1259 : claim_token,
1260 : refund_delay,
1261 : inventory_products_length,
1262 : inventory_products,
1263 : uuids_length,
1264 : uuids);
1265 : }
1266 :
1267 :
1268 : /**
1269 : * Merge the inventory products into @a order, querying the
1270 : * database about the details of those products. Upon success,
1271 : * continue processing by calling add_payment_details().
1272 : *
1273 : * @param connection connection to write the result or error to
1274 : * @param hc handler context for the request
1275 : * @param h_post_data hash of the client's POST request, for idempotency checks
1276 : * @param[in,out] order order to process (can be modified)
1277 : * @param claim_token token to use for access control
1278 : * @param refund_delay time window where it is possible to ask a refund
1279 : * @param payment_target RFC8905 payment target type to find a matching merchant account
1280 : * @param inventory_products_length length of the @a inventory_products array
1281 : * @param inventory_products array of products to add to @a order from our inventory
1282 : * @param uuids_length length of the @a uuids array
1283 : * @param uuids array of UUIDs used to reserve products from @a inventory_products
1284 : * @return MHD result code
1285 : */
1286 : static MHD_RESULT
1287 0 : merge_inventory (struct MHD_Connection *connection,
1288 : struct TMH_HandlerContext *hc,
1289 : const struct TALER_MerchantPostDataHashP *h_post_data,
1290 : json_t *order,
1291 : const struct TALER_ClaimTokenP *claim_token,
1292 : struct GNUNET_TIME_Relative refund_delay,
1293 : const char *payment_target,
1294 : unsigned int inventory_products_length,
1295 : const struct InventoryProduct inventory_products[],
1296 : unsigned int uuids_length,
1297 : const struct GNUNET_Uuid uuids[])
1298 : {
1299 : /**
1300 : * inventory_products => instructions to add products to contract terms
1301 : * order.products => contains products that are not from the backend-managed inventory.
1302 : */
1303 0 : GNUNET_assert (NULL != order);
1304 : {
1305 0 : json_t *jprod = json_object_get (order,
1306 : "products");
1307 0 : if (NULL == jprod)
1308 : {
1309 0 : GNUNET_assert (0 ==
1310 : json_object_set_new (order,
1311 : "products",
1312 : json_array ()));
1313 : }
1314 0 : else if (! TMH_products_array_valid (jprod))
1315 : {
1316 0 : return TALER_MHD_reply_with_error (connection,
1317 : MHD_HTTP_BAD_REQUEST,
1318 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1319 : "order.products");
1320 : }
1321 : }
1322 :
1323 : /* Populate products from inventory product array and database */
1324 : {
1325 0 : json_t *np = json_array ();
1326 :
1327 0 : for (unsigned int i = 0; i<inventory_products_length; i++)
1328 : {
1329 : struct TALER_MERCHANTDB_ProductDetails pd;
1330 : enum GNUNET_DB_QueryStatus qs;
1331 :
1332 0 : qs = TMH_db->lookup_product (TMH_db->cls,
1333 0 : hc->instance->settings.id,
1334 0 : inventory_products[i].product_id,
1335 : &pd);
1336 0 : if (qs <= 0)
1337 : {
1338 0 : enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
1339 0 : unsigned int http_status = 0;
1340 :
1341 0 : switch (qs)
1342 : {
1343 0 : case GNUNET_DB_STATUS_HARD_ERROR:
1344 0 : GNUNET_break (0);
1345 0 : http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
1346 0 : ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
1347 0 : break;
1348 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
1349 0 : GNUNET_break (0);
1350 0 : http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
1351 0 : ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
1352 0 : break;
1353 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1354 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1355 : "Product %s from order unknown\n",
1356 : inventory_products[i].product_id);
1357 0 : http_status = MHD_HTTP_NOT_FOUND;
1358 0 : ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
1359 0 : break;
1360 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1361 : /* case listed to make compilers happy */
1362 0 : GNUNET_assert (0);
1363 : }
1364 0 : json_decref (np);
1365 0 : return TALER_MHD_reply_with_error (connection,
1366 : http_status,
1367 : ec,
1368 0 : inventory_products[i].product_id);
1369 : }
1370 : {
1371 : json_t *p;
1372 :
1373 0 : p = GNUNET_JSON_PACK (
1374 : GNUNET_JSON_pack_string ("description",
1375 : pd.description),
1376 : GNUNET_JSON_pack_object_steal ("description_i18n",
1377 : pd.description_i18n),
1378 : GNUNET_JSON_pack_string ("unit",
1379 : pd.unit),
1380 : TALER_JSON_pack_amount ("price",
1381 : &pd.price),
1382 : GNUNET_JSON_pack_array_steal ("taxes",
1383 : pd.taxes),
1384 : GNUNET_JSON_pack_string ("image",
1385 : pd.image),
1386 : GNUNET_JSON_pack_uint64 ("quantity",
1387 : inventory_products[i].
1388 : quantity));
1389 0 : GNUNET_assert (NULL != p);
1390 0 : GNUNET_assert (0 ==
1391 : json_array_append_new (np,
1392 : p));
1393 : }
1394 0 : GNUNET_free (pd.description);
1395 0 : GNUNET_free (pd.unit);
1396 0 : GNUNET_free (pd.image);
1397 0 : json_decref (pd.address);
1398 : }
1399 : /* merge into existing products list */
1400 : {
1401 : json_t *xp;
1402 :
1403 0 : xp = json_object_get (order,
1404 : "products");
1405 0 : GNUNET_assert (NULL != xp);
1406 0 : json_array_extend (xp, np);
1407 0 : json_decref (np);
1408 : }
1409 : }
1410 0 : return add_payment_details (connection,
1411 : hc,
1412 : h_post_data,
1413 : order,
1414 : claim_token,
1415 : refund_delay,
1416 : payment_target,
1417 : inventory_products_length,
1418 : inventory_products,
1419 : uuids_length,
1420 : uuids);
1421 : }
1422 :
1423 :
1424 : /**
1425 : * Generate an order. We add the fields 'exchanges', 'merchant_pub', and
1426 : * 'H_wire' to the order gotten from the frontend, as well as possibly other
1427 : * fields if the frontend did not provide them. Returns the order_id.
1428 : *
1429 : * @param rh context of the handler
1430 : * @param connection the MHD connection to handle
1431 : * @param[in,out] hc context with further information about the request
1432 : * @return MHD result code
1433 : */
1434 : MHD_RESULT
1435 0 : TMH_private_post_orders (const struct TMH_RequestHandler *rh,
1436 : struct MHD_Connection *connection,
1437 : struct TMH_HandlerContext *hc)
1438 : {
1439 : json_t *order;
1440 0 : struct GNUNET_TIME_Relative refund_delay = GNUNET_TIME_UNIT_ZERO;
1441 0 : const char *payment_target = NULL;
1442 0 : json_t *ip = NULL;
1443 0 : unsigned int ips_len = 0;
1444 0 : struct InventoryProduct *ips = NULL;
1445 0 : unsigned int uuids_len = 0;
1446 : json_t *uuid;
1447 0 : struct GNUNET_Uuid *uuids = NULL;
1448 : struct TALER_ClaimTokenP claim_token;
1449 0 : bool create_token = true; /* default */
1450 : struct GNUNET_JSON_Specification spec[] = {
1451 0 : GNUNET_JSON_spec_json ("order",
1452 : &order),
1453 0 : GNUNET_JSON_spec_mark_optional (
1454 : GNUNET_JSON_spec_relative_time ("refund_delay",
1455 : &refund_delay),
1456 : NULL),
1457 0 : GNUNET_JSON_spec_mark_optional (
1458 : GNUNET_JSON_spec_string ("payment_target",
1459 : &payment_target),
1460 : NULL),
1461 0 : GNUNET_JSON_spec_mark_optional (
1462 : GNUNET_JSON_spec_json ("inventory_products",
1463 : &ip),
1464 : NULL),
1465 0 : GNUNET_JSON_spec_mark_optional (
1466 : GNUNET_JSON_spec_json ("lock_uuids",
1467 : &uuid),
1468 : NULL),
1469 0 : GNUNET_JSON_spec_mark_optional (
1470 : GNUNET_JSON_spec_bool ("create_token",
1471 : &create_token),
1472 : NULL),
1473 0 : GNUNET_JSON_spec_end ()
1474 : };
1475 : enum GNUNET_GenericReturnValue ret;
1476 : struct TALER_MerchantPostDataHashP h_post_data;
1477 :
1478 : (void) rh;
1479 0 : ret = TALER_MHD_parse_json_data (connection,
1480 0 : hc->request_body,
1481 : spec);
1482 0 : if (GNUNET_OK != ret)
1483 : return (GNUNET_NO == ret)
1484 : ? MHD_YES
1485 0 : : MHD_NO;
1486 :
1487 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1488 : "Refund delay is %s\n",
1489 : GNUNET_TIME_relative2s (refund_delay,
1490 : false));
1491 :
1492 0 : TMH_db->expire_locks (TMH_db->cls);
1493 0 : if (create_token)
1494 : {
1495 0 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
1496 : &claim_token,
1497 : sizeof (claim_token));
1498 : }
1499 : else
1500 : {
1501 : /* we use all-zeros for 'no token' */
1502 0 : memset (&claim_token,
1503 : 0,
1504 : sizeof (claim_token));
1505 : }
1506 :
1507 : /* Compute h_post_data (for idempotency check) */
1508 : {
1509 : char *req_body_enc;
1510 :
1511 : /* Dump normalized JSON to string. */
1512 0 : if (NULL == (req_body_enc = json_dumps (hc->request_body,
1513 : JSON_ENCODE_ANY
1514 : | JSON_COMPACT
1515 : | JSON_SORT_KEYS)))
1516 : {
1517 0 : GNUNET_break (0);
1518 0 : GNUNET_JSON_parse_free (spec);
1519 0 : return TALER_MHD_reply_with_error (connection,
1520 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1521 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1522 : "request body normalization for hashing");
1523 : }
1524 0 : GNUNET_CRYPTO_hash (req_body_enc,
1525 : strlen (req_body_enc),
1526 : &h_post_data.hash);
1527 0 : GNUNET_free (req_body_enc);
1528 : }
1529 :
1530 : /* parse the inventory_products (optionally given) */
1531 0 : if (NULL != ip)
1532 : {
1533 0 : if (! json_is_array (ip))
1534 : {
1535 0 : GNUNET_JSON_parse_free (spec);
1536 0 : return TALER_MHD_reply_with_error (connection,
1537 : MHD_HTTP_BAD_REQUEST,
1538 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1539 : "inventory_products");
1540 : }
1541 0 : GNUNET_array_grow (ips,
1542 : ips_len,
1543 : json_array_size (ip));
1544 0 : for (unsigned int i = 0; i<ips_len; i++)
1545 : {
1546 : const char *error_name;
1547 : unsigned int error_line;
1548 : struct GNUNET_JSON_Specification ispec[] = {
1549 0 : GNUNET_JSON_spec_string ("product_id",
1550 0 : &ips[i].product_id),
1551 0 : GNUNET_JSON_spec_uint32 ("quantity",
1552 0 : &ips[i].quantity),
1553 0 : GNUNET_JSON_spec_end ()
1554 : };
1555 :
1556 0 : ret = GNUNET_JSON_parse (json_array_get (ip,
1557 : i),
1558 : ispec,
1559 : &error_name,
1560 : &error_line);
1561 0 : if (GNUNET_OK != ret)
1562 : {
1563 0 : GNUNET_break_op (0);
1564 0 : GNUNET_array_grow (ips,
1565 : ips_len,
1566 : 0);
1567 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1568 : "Product parsing failed at #%u: %s:%u\n",
1569 : i,
1570 : error_name,
1571 : error_line);
1572 0 : GNUNET_JSON_parse_free (spec);
1573 0 : return TALER_MHD_reply_with_error (connection,
1574 : MHD_HTTP_BAD_REQUEST,
1575 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1576 : "inventory_products");
1577 : }
1578 : }
1579 : }
1580 :
1581 : /* parse the lock_uuids (optionally given) */
1582 0 : if (NULL != uuid)
1583 : {
1584 0 : if (! json_is_array (uuid))
1585 : {
1586 0 : GNUNET_array_grow (ips,
1587 : ips_len,
1588 : 0);
1589 0 : GNUNET_JSON_parse_free (spec);
1590 0 : return TALER_MHD_reply_with_error (connection,
1591 : MHD_HTTP_BAD_REQUEST,
1592 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1593 : "lock_uuids");
1594 : }
1595 0 : GNUNET_array_grow (uuids,
1596 : uuids_len,
1597 : json_array_size (uuid));
1598 0 : for (unsigned int i = 0; i<uuids_len; i++)
1599 : {
1600 0 : json_t *ui = json_array_get (uuid,
1601 : i);
1602 :
1603 0 : if (! json_is_string (ui))
1604 : {
1605 0 : GNUNET_break_op (0);
1606 0 : GNUNET_array_grow (ips,
1607 : ips_len,
1608 : 0);
1609 0 : GNUNET_array_grow (uuids,
1610 : uuids_len,
1611 : 0);
1612 0 : GNUNET_JSON_parse_free (spec);
1613 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1614 : "UUID parsing failed at #%u\n",
1615 : i);
1616 0 : return TALER_MHD_reply_with_error (connection,
1617 : MHD_HTTP_BAD_REQUEST,
1618 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1619 : "lock_uuids");
1620 : }
1621 0 : TMH_uuid_from_string (json_string_value (ui),
1622 0 : &uuids[i]);
1623 : }
1624 : }
1625 :
1626 : /* Finally, start by completing the order */
1627 : {
1628 : MHD_RESULT res;
1629 :
1630 0 : res = merge_inventory (connection,
1631 : hc,
1632 : &h_post_data,
1633 : order,
1634 : &claim_token,
1635 : refund_delay,
1636 : payment_target,
1637 : ips_len,
1638 : ips,
1639 : uuids_len,
1640 : uuids);
1641 0 : GNUNET_array_grow (ips,
1642 : ips_len,
1643 : 0);
1644 0 : GNUNET_array_grow (uuids,
1645 : uuids_len,
1646 : 0);
1647 0 : GNUNET_JSON_parse_free (spec);
1648 0 : return res;
1649 : }
1650 : }
1651 :
1652 :
1653 : /* end of taler-merchant-httpd_private-post-orders.c */
|