Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Lesser General Public License as published by the Free Software
7 : Foundation; either version 2.1, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
12 :
13 : You should have received a copy of the GNU Lesser General Public License along with
14 : TALER; see the file COPYING.LGPL. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file src/lib/merchant_api_post-private-orders.c
19 : * @brief Implementation of the POST /private/orders request
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <curl/curl.h>
24 : #include <jansson.h>
25 : #include <microhttpd.h> /* just for HTTP status codes */
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include <taler/merchant/post-private-orders.h>
29 : #include "merchant_api_curl_defaults.h"
30 : #include "merchant_api_common.h"
31 : #include <taler/taler_json_lib.h>
32 : #include <taler/taler_curl_lib.h>
33 :
34 :
35 : /**
36 : * Maximum number of exchange rejections we allow in a response before
37 : * we consider the (untrusted) reply malformed. Bounds the stack VLA
38 : * used to parse the array.
39 : */
40 : #define MAX_EXCHANGE_REJECTIONS 1024
41 :
42 :
43 : /**
44 : * Handle for a POST /private/orders operation.
45 : */
46 : struct TALER_MERCHANT_PostPrivateOrdersHandle
47 : {
48 : /**
49 : * Base URL of the merchant backend.
50 : */
51 : char *base_url;
52 :
53 : /**
54 : * The full URL for this request.
55 : */
56 : char *url;
57 :
58 : /**
59 : * Handle for the request.
60 : */
61 : struct GNUNET_CURL_Job *job;
62 :
63 : /**
64 : * Function to call with the result.
65 : */
66 : TALER_MERCHANT_PostPrivateOrdersCallback cb;
67 :
68 : /**
69 : * Closure for @a cb.
70 : */
71 : TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls;
72 :
73 : /**
74 : * Reference to the execution context.
75 : */
76 : struct GNUNET_CURL_Context *ctx;
77 :
78 : /**
79 : * Minor context that holds body and headers.
80 : */
81 : struct TALER_CURL_PostContext post_ctx;
82 :
83 : /**
84 : * Order contract terms (JSON).
85 : */
86 : json_t *order;
87 :
88 : /**
89 : * Optional refund delay.
90 : */
91 : struct GNUNET_TIME_Relative refund_delay;
92 :
93 : /**
94 : * Whether refund_delay was set.
95 : */
96 : bool refund_delay_set;
97 :
98 : /**
99 : * Optional payment target.
100 : */
101 : const char *payment_target;
102 :
103 : /**
104 : * Optional session ID.
105 : */
106 : const char *session_id;
107 :
108 : /**
109 : * Whether to create a claim token (default: true).
110 : */
111 : bool create_token;
112 :
113 : /**
114 : * Optional OTP device ID.
115 : */
116 : const char *otp_id;
117 :
118 : /**
119 : * Optional inventory products.
120 : */
121 : const struct TALER_MERCHANT_PostPrivateOrdersInventoryProduct *
122 : inventory_products;
123 :
124 : /**
125 : * Number of inventory products.
126 : */
127 : unsigned int num_inventory_products;
128 :
129 : /**
130 : * Optional lock UUIDs.
131 : */
132 : const char **lock_uuids;
133 :
134 : /**
135 : * Number of lock UUIDs.
136 : */
137 : unsigned int num_lock_uuids;
138 : };
139 :
140 :
141 : /**
142 : * Function called when we're done processing the
143 : * HTTP POST /private/orders request.
144 : *
145 : * @param cls the `struct TALER_MERCHANT_PostPrivateOrdersHandle`
146 : * @param response_code HTTP response code, 0 on error
147 : * @param response response body, NULL if not in JSON
148 : */
149 : static void
150 81 : handle_post_orders_finished (void *cls,
151 : long response_code,
152 : const void *response)
153 : {
154 81 : struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh = cls;
155 81 : const json_t *json = response;
156 81 : struct TALER_MERCHANT_PostPrivateOrdersResponse por = {
157 81 : .hr.http_status = (unsigned int) response_code,
158 : .hr.reply = json
159 : };
160 : struct TALER_ClaimTokenP token;
161 :
162 81 : ppoh->job = NULL;
163 81 : switch (response_code)
164 : {
165 0 : case 0:
166 0 : por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
167 0 : break;
168 63 : case MHD_HTTP_OK:
169 : {
170 : bool no_token;
171 : bool no_pay_deadline;
172 : struct GNUNET_JSON_Specification spec[] = {
173 63 : TALER_JSON_spec_slug ("order_id",
174 : &por.details.ok.order_id),
175 63 : GNUNET_JSON_spec_mark_optional (
176 : GNUNET_JSON_spec_fixed_auto ("token",
177 : &token),
178 : &no_token),
179 63 : GNUNET_JSON_spec_mark_optional (
180 : GNUNET_JSON_spec_timestamp ("pay_deadline",
181 : &por.details.ok.pay_deadline),
182 : &no_pay_deadline),
183 63 : GNUNET_JSON_spec_end ()
184 : };
185 :
186 63 : if (GNUNET_OK !=
187 63 : GNUNET_JSON_parse (json,
188 : spec,
189 : NULL, NULL))
190 : {
191 0 : GNUNET_break_op (0);
192 0 : por.hr.http_status = 0;
193 0 : por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
194 0 : break;
195 : }
196 63 : if (! no_token)
197 59 : por.details.ok.token = &token;
198 63 : if (no_pay_deadline)
199 0 : por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
200 63 : break;
201 : }
202 8 : case MHD_HTTP_BAD_REQUEST:
203 8 : por.hr.ec = TALER_JSON_get_error_code (json);
204 8 : por.hr.hint = TALER_JSON_get_error_hint (json);
205 8 : break;
206 0 : case MHD_HTTP_UNAUTHORIZED:
207 0 : por.hr.ec = TALER_JSON_get_error_code (json);
208 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
209 0 : break;
210 0 : case MHD_HTTP_FORBIDDEN:
211 0 : por.hr.ec = TALER_JSON_get_error_code (json);
212 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
213 0 : break;
214 4 : case MHD_HTTP_NOT_FOUND:
215 4 : por.hr.ec = TALER_JSON_get_error_code (json);
216 4 : por.hr.hint = TALER_JSON_get_error_hint (json);
217 4 : break;
218 4 : case MHD_HTTP_CONFLICT:
219 4 : por.hr.ec = TALER_JSON_get_error_code (json);
220 4 : por.hr.hint = TALER_JSON_get_error_hint (json);
221 4 : break;
222 2 : case MHD_HTTP_GONE:
223 : {
224 : bool rq_frac_missing;
225 : bool aq_frac_missing;
226 : struct GNUNET_JSON_Specification spec[] = {
227 2 : TALER_JSON_spec_slug (
228 : "product_id",
229 : &por.details.gone.product_id),
230 2 : GNUNET_JSON_spec_uint64 (
231 : "requested_quantity",
232 : &por.details.gone.requested_quantity),
233 2 : GNUNET_JSON_spec_mark_optional (
234 : GNUNET_JSON_spec_uint32 (
235 : "requested_quantity_frac",
236 : &por.details.gone.requested_quantity_frac),
237 : &rq_frac_missing),
238 2 : GNUNET_JSON_spec_uint64 (
239 : "available_quantity",
240 : &por.details.gone.available_quantity),
241 2 : GNUNET_JSON_spec_mark_optional (
242 : GNUNET_JSON_spec_uint32 (
243 : "available_quantity_frac",
244 : &por.details.gone.available_quantity_frac),
245 : &aq_frac_missing),
246 2 : GNUNET_JSON_spec_mark_optional (
247 : GNUNET_JSON_spec_string (
248 : "unit_requested_quantity",
249 : &por.details.gone.unit_requested_quantity),
250 : NULL),
251 2 : GNUNET_JSON_spec_mark_optional (
252 : GNUNET_JSON_spec_string (
253 : "unit_available_quantity",
254 : &por.details.gone.unit_available_quantity),
255 : NULL),
256 2 : GNUNET_JSON_spec_mark_optional (
257 : GNUNET_JSON_spec_timestamp (
258 : "restock_expected",
259 : &por.details.gone.restock_expected),
260 : NULL),
261 2 : GNUNET_JSON_spec_end ()
262 : };
263 :
264 2 : if (GNUNET_OK !=
265 2 : GNUNET_JSON_parse (json,
266 : spec,
267 : NULL, NULL))
268 : {
269 0 : GNUNET_break_op (0);
270 0 : por.hr.http_status = 0;
271 0 : por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
272 : }
273 : else
274 : {
275 2 : if (rq_frac_missing)
276 2 : por.details.gone.requested_quantity_frac = 0;
277 2 : if (aq_frac_missing)
278 2 : por.details.gone.available_quantity_frac = 0;
279 : }
280 2 : break;
281 : }
282 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
283 : {
284 0 : const json_t *jer = NULL;
285 :
286 0 : por.hr.ec = TALER_JSON_get_error_code (json);
287 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
288 0 : jer = json_object_get (json,
289 : "exchange_rejections");
290 0 : if ( (NULL != jer) &&
291 0 : json_is_array (jer) )
292 : {
293 0 : unsigned int rej_len = (unsigned int) json_array_size (jer);
294 :
295 0 : if ( (json_array_size (jer) == (size_t) rej_len) &&
296 : (rej_len <= MAX_EXCHANGE_REJECTIONS) )
297 0 : {
298 0 : struct TALER_MERCHANT_ExchangeRejectionDetail rejs[
299 0 : GNUNET_NZL (rej_len)];
300 0 : bool ok = true;
301 :
302 0 : memset (rejs, 0, sizeof (rejs));
303 0 : for (unsigned int i = 0; i < rej_len; i++)
304 : {
305 : struct GNUNET_JSON_Specification rspec[] = {
306 0 : TALER_JSON_spec_web_url (
307 : "exchange_url",
308 : &rejs[i].exchange_url),
309 0 : TALER_JSON_spec_ec (
310 : "code",
311 : &rejs[i].code),
312 0 : GNUNET_JSON_spec_mark_optional (
313 : GNUNET_JSON_spec_string (
314 : "hint",
315 : &rejs[i].hint),
316 : NULL),
317 0 : GNUNET_JSON_spec_end ()
318 : };
319 :
320 0 : if (GNUNET_OK !=
321 0 : GNUNET_JSON_parse (json_array_get (jer, i),
322 : rspec,
323 : NULL, NULL))
324 : {
325 0 : GNUNET_break_op (0);
326 0 : ok = false;
327 0 : break;
328 : }
329 : }
330 0 : if (ok)
331 : {
332 : por.details.unavailable_for_legal_reasons
333 0 : .num_exchange_rejections = rej_len;
334 : por.details.unavailable_for_legal_reasons
335 0 : .exchange_rejections = rejs;
336 0 : ppoh->cb (ppoh->cb_cls,
337 : &por);
338 0 : TALER_MERCHANT_post_private_orders_cancel (ppoh);
339 0 : return;
340 : }
341 : }
342 : }
343 0 : break;
344 : }
345 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
346 0 : por.hr.ec = TALER_JSON_get_error_code (json);
347 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
348 0 : break;
349 0 : default:
350 0 : por.hr.ec = TALER_JSON_get_error_code (json);
351 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
352 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
353 : "Unexpected response code %u/%d\n",
354 : (unsigned int) response_code,
355 : (int) por.hr.ec);
356 0 : GNUNET_break_op (0);
357 0 : break;
358 : }
359 81 : ppoh->cb (ppoh->cb_cls,
360 : &por);
361 81 : TALER_MERCHANT_post_private_orders_cancel (ppoh);
362 : }
363 :
364 :
365 : struct TALER_MERCHANT_PostPrivateOrdersHandle *
366 81 : TALER_MERCHANT_post_private_orders_create (
367 : struct GNUNET_CURL_Context *ctx,
368 : const char *url,
369 : const json_t *order)
370 : {
371 : struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh;
372 :
373 81 : ppoh = GNUNET_new (struct TALER_MERCHANT_PostPrivateOrdersHandle);
374 81 : ppoh->ctx = ctx;
375 81 : ppoh->base_url = GNUNET_strdup (url);
376 81 : ppoh->order = json_incref ((json_t *) order);
377 81 : ppoh->create_token = true;
378 81 : return ppoh;
379 : }
380 :
381 :
382 : enum GNUNET_GenericReturnValue
383 62 : TALER_MERCHANT_post_private_orders_set_options_ (
384 : struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh,
385 : unsigned int num_options,
386 : const struct TALER_MERCHANT_PostPrivateOrdersOptionValue *options)
387 : {
388 124 : for (unsigned int i = 0; i < num_options; i++)
389 : {
390 124 : switch (options[i].option)
391 : {
392 62 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_END:
393 62 : return GNUNET_OK;
394 0 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_REFUND_DELAY:
395 0 : ppoh->refund_delay = options[i].details.refund_delay;
396 0 : ppoh->refund_delay_set = true;
397 0 : break;
398 23 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_PAYMENT_TARGET:
399 23 : ppoh->payment_target = options[i].details.payment_target;
400 23 : break;
401 0 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_SESSION_ID:
402 0 : ppoh->session_id = options[i].details.session_id;
403 0 : break;
404 23 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_CREATE_TOKEN:
405 23 : ppoh->create_token = options[i].details.create_token;
406 23 : break;
407 0 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_OTP_ID:
408 0 : ppoh->otp_id = options[i].details.otp_id;
409 0 : break;
410 14 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_INVENTORY_PRODUCTS:
411 : ppoh->num_inventory_products
412 14 : = options[i].details.inventory_products.num;
413 : ppoh->inventory_products
414 14 : = options[i].details.inventory_products.products;
415 14 : break;
416 2 : case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_LOCK_UUIDS:
417 2 : ppoh->num_lock_uuids = options[i].details.lock_uuids.num;
418 2 : ppoh->lock_uuids = options[i].details.lock_uuids.uuids;
419 2 : break;
420 0 : default:
421 0 : GNUNET_break (0);
422 0 : return GNUNET_SYSERR;
423 : }
424 : }
425 0 : return GNUNET_OK;
426 : }
427 :
428 :
429 : enum TALER_ErrorCode
430 81 : TALER_MERCHANT_post_private_orders_start (
431 : struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh,
432 : TALER_MERCHANT_PostPrivateOrdersCallback cb,
433 : TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls)
434 : {
435 : json_t *req;
436 : CURL *eh;
437 :
438 81 : ppoh->cb = cb;
439 81 : ppoh->cb_cls = cb_cls;
440 81 : ppoh->url = TALER_url_join (ppoh->base_url,
441 : "private/orders",
442 : NULL);
443 81 : if (NULL == ppoh->url)
444 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
445 81 : req = GNUNET_JSON_PACK (
446 : GNUNET_JSON_pack_object_incref ("order",
447 : ppoh->order),
448 : GNUNET_JSON_pack_allow_null (
449 : GNUNET_JSON_pack_string ("session_id",
450 : ppoh->session_id)),
451 : GNUNET_JSON_pack_allow_null (
452 : GNUNET_JSON_pack_string ("payment_target",
453 : ppoh->payment_target)),
454 : GNUNET_JSON_pack_allow_null (
455 : GNUNET_JSON_pack_string ("otp_id",
456 : ppoh->otp_id)));
457 81 : if (ppoh->refund_delay_set &&
458 0 : (0 != ppoh->refund_delay.rel_value_us))
459 : {
460 0 : GNUNET_assert (0 ==
461 : json_object_set_new (req,
462 : "refund_delay",
463 : GNUNET_JSON_from_time_rel (
464 : ppoh->refund_delay)));
465 : }
466 81 : if (0 != ppoh->num_inventory_products)
467 : {
468 14 : json_t *ipa = json_array ();
469 :
470 14 : GNUNET_assert (NULL != ipa);
471 28 : for (unsigned int i = 0; i < ppoh->num_inventory_products; i++)
472 : {
473 : json_t *ip;
474 :
475 : {
476 : char unit_quantity_buf[64];
477 :
478 10 : TALER_MERCHANT_format_quantity_string (
479 14 : ppoh->inventory_products[i].quantity,
480 14 : ppoh->inventory_products[i].use_fractional_quantity
481 4 : ? ppoh->inventory_products[i].quantity_frac
482 : : 0,
483 : unit_quantity_buf,
484 : sizeof (unit_quantity_buf));
485 14 : ip = GNUNET_JSON_PACK (
486 : GNUNET_JSON_pack_string ("product_id",
487 : ppoh->inventory_products[i].product_id),
488 : GNUNET_JSON_pack_string ("unit_quantity",
489 : unit_quantity_buf));
490 : }
491 14 : if (ppoh->inventory_products[i].product_money_pot > 0)
492 : {
493 0 : GNUNET_assert (
494 : 0 ==
495 : json_object_set_new (
496 : ip,
497 : "product_money_pot",
498 : json_integer (
499 : ppoh->inventory_products[i].product_money_pot)));
500 : }
501 14 : GNUNET_assert (0 ==
502 : json_array_append_new (ipa,
503 : ip));
504 : }
505 14 : GNUNET_assert (0 ==
506 : json_object_set_new (req,
507 : "inventory_products",
508 : ipa));
509 : }
510 81 : if (0 != ppoh->num_lock_uuids)
511 : {
512 2 : json_t *ua = json_array ();
513 :
514 2 : GNUNET_assert (NULL != ua);
515 4 : for (unsigned int i = 0; i < ppoh->num_lock_uuids; i++)
516 : {
517 2 : GNUNET_assert (0 ==
518 : json_array_append_new (ua,
519 : json_string (
520 : ppoh->lock_uuids[i])));
521 : }
522 2 : GNUNET_assert (0 ==
523 : json_object_set_new (req,
524 : "lock_uuids",
525 : ua));
526 : }
527 81 : if (! ppoh->create_token)
528 : {
529 4 : GNUNET_assert (0 ==
530 : json_object_set_new (req,
531 : "create_token",
532 : json_boolean (ppoh->create_token)));
533 : }
534 81 : eh = TALER_MERCHANT_curl_easy_get_ (ppoh->url);
535 162 : if ( (NULL == eh) ||
536 : (GNUNET_OK !=
537 81 : TALER_curl_easy_post (&ppoh->post_ctx,
538 : eh,
539 : req)) )
540 : {
541 0 : GNUNET_break (0);
542 0 : json_decref (req);
543 0 : if (NULL != eh)
544 0 : curl_easy_cleanup (eh);
545 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
546 : }
547 81 : json_decref (req);
548 162 : ppoh->job = GNUNET_CURL_job_add2 (ppoh->ctx,
549 : eh,
550 81 : ppoh->post_ctx.headers,
551 : &handle_post_orders_finished,
552 : ppoh);
553 81 : if (NULL == ppoh->job)
554 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
555 81 : return TALER_EC_NONE;
556 : }
557 :
558 :
559 : void
560 81 : TALER_MERCHANT_post_private_orders_cancel (
561 : struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh)
562 : {
563 81 : if (NULL != ppoh->job)
564 : {
565 0 : GNUNET_CURL_job_cancel (ppoh->job);
566 0 : ppoh->job = NULL;
567 : }
568 81 : TALER_curl_easy_post_finished (&ppoh->post_ctx);
569 81 : json_decref (ppoh->order);
570 81 : GNUNET_free (ppoh->url);
571 81 : GNUNET_free (ppoh->base_url);
572 81 : GNUNET_free (ppoh);
573 81 : }
574 :
575 :
576 : /* end of merchant_api_post-private-orders-new.c */
|