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