Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020-2023 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_common.c
19 : * @brief Implementation of common logic for libtalermerchant
20 : * @author Christian Grothoff
21 : * @author Priscilla Huang
22 : */
23 : #include "platform.h"
24 : #include "microhttpd.h"
25 : #include <curl/curl.h>
26 : #include "taler_merchant_service.h"
27 : #include "merchant_api_common.h"
28 : #include <gnunet/gnunet_uri_lib.h>
29 : #include <taler/taler_json_lib.h>
30 : #include "taler_merchant_util.h"
31 :
32 :
33 : static void
34 94 : TALER_MERCHANT_format_fractional_string (uint64_t integer,
35 : uint32_t fractional,
36 : char *buffer,
37 : size_t buffer_length)
38 : {
39 94 : GNUNET_assert (NULL != buffer);
40 94 : GNUNET_assert (0 < buffer_length);
41 94 : GNUNET_assert (fractional < MERCHANT_UNIT_FRAC_BASE);
42 :
43 94 : if (0 == fractional)
44 : {
45 78 : GNUNET_snprintf (buffer,
46 : buffer_length,
47 : "%lu",
48 : integer);
49 78 : return;
50 : }
51 : {
52 : char frac_buf[MERCHANT_UNIT_FRAC_MAX_DIGITS + 1];
53 : size_t idx;
54 :
55 16 : GNUNET_snprintf (frac_buf,
56 : sizeof (frac_buf),
57 : "%0*u",
58 : MERCHANT_UNIT_FRAC_MAX_DIGITS,
59 : (unsigned int) fractional);
60 88 : for (idx = strlen (frac_buf); idx > 0; idx--)
61 : {
62 88 : if ('0' != frac_buf[idx - 1])
63 16 : break;
64 72 : frac_buf[idx - 1] = '\0';
65 : }
66 16 : if ('\0' == frac_buf[0])
67 0 : GNUNET_strlcpy (frac_buf,
68 : "0",
69 : sizeof (frac_buf));
70 16 : GNUNET_snprintf (buffer,
71 : buffer_length,
72 : "%lu.%s",
73 : integer,
74 : frac_buf);
75 : }
76 : }
77 :
78 :
79 : void
80 26 : TALER_MERCHANT_format_quantity_string (uint64_t quantity,
81 : uint32_t quantity_frac,
82 : char *buffer,
83 : size_t buffer_length)
84 : {
85 26 : TALER_MERCHANT_format_fractional_string (quantity,
86 : quantity_frac,
87 : buffer,
88 : buffer_length);
89 26 : }
90 :
91 :
92 : void
93 68 : TALER_MERCHANT_format_stock_string (uint64_t total_stock,
94 : uint32_t total_stock_frac,
95 : char *buffer,
96 : size_t buffer_length)
97 : {
98 68 : if ( (INT64_MAX == (int64_t) total_stock) &&
99 : (INT32_MAX == (int32_t) total_stock_frac) )
100 : {
101 0 : GNUNET_snprintf (buffer,
102 : buffer_length,
103 : "-1");
104 0 : return;
105 : }
106 68 : TALER_MERCHANT_format_fractional_string (total_stock,
107 : total_stock_frac,
108 : buffer,
109 : buffer_length);
110 : }
111 :
112 :
113 : void
114 10 : TALER_MERCHANT_parse_error_details_ (const json_t *response,
115 : unsigned int http_status,
116 : struct TALER_MERCHANT_HttpResponse *hr)
117 : {
118 : const json_t *jc;
119 :
120 10 : memset (hr, 0, sizeof (*hr));
121 10 : hr->reply = response;
122 10 : hr->http_status = http_status;
123 10 : if (NULL == response)
124 : {
125 0 : hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE;
126 0 : return;
127 : }
128 10 : hr->ec = TALER_JSON_get_error_code (response);
129 10 : hr->hint = TALER_JSON_get_error_hint (response);
130 :
131 : /* handle 'exchange_http_status' */
132 10 : jc = json_object_get (response,
133 : "exchange_http_status");
134 : /* The caller already knows that the JSON represents an error,
135 : so we are dealing with a missing error code here. */
136 10 : if (NULL == jc)
137 4 : return; /* no need to bother with exchange_code/hint if we had no status */
138 6 : if (! json_is_integer (jc))
139 : {
140 0 : GNUNET_break_op (0);
141 0 : return;
142 : }
143 6 : hr->exchange_http_status = (unsigned int) json_integer_value (jc);
144 :
145 : /* handle 'exchange_reply' */
146 6 : jc = json_object_get (response,
147 : "exchange_reply");
148 6 : if (! json_is_object (jc))
149 : {
150 0 : GNUNET_break_op (0);
151 : }
152 : else
153 : {
154 6 : hr->exchange_reply = jc;
155 : }
156 :
157 : /* handle 'exchange_code' */
158 6 : jc = json_object_get (response,
159 : "exchange_code");
160 : /* The caller already knows that the JSON represents an error,
161 : so we are dealing with a missing error code here. */
162 6 : if (NULL == jc)
163 0 : return; /* no need to bother with exchange-hint if we had no code */
164 6 : if (! json_is_integer (jc))
165 : {
166 0 : GNUNET_break_op (0);
167 0 : hr->exchange_code = TALER_EC_INVALID;
168 : }
169 : else
170 : {
171 6 : hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc);
172 : }
173 :
174 : /* handle 'exchange-hint' */
175 6 : jc = json_object_get (response,
176 : "exchange-hint");
177 : /* The caller already knows that the JSON represents an error,
178 : so we are dealing with a missing error code here. */
179 6 : if (NULL == jc)
180 6 : return;
181 0 : if (! json_is_string (jc))
182 : {
183 0 : GNUNET_break_op (0);
184 : }
185 : else
186 : {
187 0 : hr->exchange_hint = json_string_value (jc);
188 : }
189 : }
190 :
191 :
192 : enum GNUNET_GenericReturnValue
193 20 : TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
194 : struct TALER_MERCHANT_PayUriData *parse_data)
195 : {
196 20 : char *cp = GNUNET_strdup (pay_uri);
197 : struct GNUNET_Uri u;
198 :
199 20 : if (0 !=
200 20 : GNUNET_uri_parse (&u,
201 : cp))
202 : {
203 0 : GNUNET_free (cp);
204 0 : GNUNET_break_op (0);
205 0 : return GNUNET_SYSERR;
206 : }
207 20 : if ((0 != strcasecmp ("taler",
208 20 : u.scheme)) &&
209 16 : (0 != strcasecmp ("taler+http",
210 16 : u.scheme)))
211 : {
212 1 : fprintf (stderr,
213 : "Bad schema %s\n",
214 : u.scheme);
215 1 : GNUNET_free (cp);
216 1 : GNUNET_break_op (0);
217 1 : return GNUNET_SYSERR;
218 : }
219 19 : parse_data->use_http = (0 == strcasecmp ("taler+http",
220 19 : u.scheme));
221 19 : if (0 != strcasecmp ("pay",
222 19 : u.host))
223 : {
224 1 : GNUNET_break_op (0);
225 1 : GNUNET_free (cp);
226 1 : return GNUNET_SYSERR;
227 : }
228 :
229 : {
230 : char *order_id;
231 : char *mpp;
232 18 : char *session_id = strrchr (u.path,
233 : '/');
234 18 : struct TALER_ClaimTokenP *claim_token = NULL;
235 :
236 18 : if (NULL == session_id)
237 : {
238 0 : GNUNET_break_op (0);
239 0 : GNUNET_free (cp);
240 0 : return GNUNET_SYSERR;
241 : }
242 18 : *session_id = '\0';
243 18 : ++session_id;
244 :
245 18 : order_id = strrchr (u.path,
246 : '/');
247 18 : if (NULL == order_id)
248 : {
249 1 : GNUNET_break_op (0);
250 1 : GNUNET_free (cp);
251 1 : return GNUNET_SYSERR;
252 : }
253 17 : *order_id = '\0';
254 17 : ++order_id;
255 17 : mpp = strchr (u.path,
256 : '/');
257 17 : if (NULL != mpp)
258 : {
259 1 : *mpp = '\0';
260 1 : ++mpp;
261 : }
262 :
263 : {
264 17 : char *ct_str = u.query;
265 : char *ct_data;
266 :
267 17 : if (NULL != ct_str)
268 : {
269 7 : ct_data = strchr (u.query,
270 : '=');
271 7 : if (NULL == ct_data)
272 : {
273 0 : GNUNET_break_op (0);
274 0 : GNUNET_free (cp);
275 0 : return GNUNET_SYSERR;
276 : }
277 7 : *ct_data = '\0';
278 7 : ++ct_data;
279 7 : claim_token = GNUNET_new (struct TALER_ClaimTokenP);
280 7 : if ( (0 != strcmp ("c",
281 14 : u.query)) ||
282 : (GNUNET_OK !=
283 7 : GNUNET_STRINGS_string_to_data (ct_data,
284 : strlen (ct_data),
285 : claim_token,
286 : sizeof (*claim_token))) )
287 : {
288 0 : GNUNET_break_op (0);
289 0 : GNUNET_free (claim_token);
290 0 : GNUNET_free (cp);
291 0 : return GNUNET_SYSERR;
292 : }
293 : }
294 : }
295 :
296 : parse_data->merchant_prefix_path
297 17 : = (NULL == mpp)
298 : ? NULL
299 17 : : GNUNET_strdup (mpp);
300 17 : parse_data->merchant_host = GNUNET_strdup (u.path);
301 17 : parse_data->order_id = GNUNET_strdup (order_id);
302 : parse_data->session_id
303 34 : = (0 < strlen (session_id))
304 10 : ? GNUNET_strdup (session_id)
305 17 : : NULL;
306 17 : parse_data->claim_token = claim_token;
307 : parse_data->ssid
308 34 : = (NULL == u.fragment)
309 : ? NULL
310 17 : : GNUNET_strdup (u.fragment);
311 : }
312 17 : GNUNET_free (cp);
313 17 : return GNUNET_OK;
314 : }
315 :
316 :
317 : void
318 17 : TALER_MERCHANT_parse_pay_uri_free (
319 : struct TALER_MERCHANT_PayUriData *parse_data)
320 : {
321 17 : GNUNET_free (parse_data->merchant_host);
322 17 : GNUNET_free (parse_data->merchant_prefix_path);
323 17 : GNUNET_free (parse_data->order_id);
324 17 : GNUNET_free (parse_data->session_id);
325 17 : GNUNET_free (parse_data->claim_token);
326 17 : GNUNET_free (parse_data->ssid);
327 17 : }
328 :
329 :
330 : enum GNUNET_GenericReturnValue
331 11 : TALER_MERCHANT_parse_refund_uri (
332 : const char *refund_uri,
333 : struct TALER_MERCHANT_RefundUriData *parse_data)
334 : {
335 11 : char *cp = GNUNET_strdup (refund_uri);
336 : struct GNUNET_Uri u;
337 :
338 11 : if (0 !=
339 11 : GNUNET_uri_parse (&u,
340 : cp))
341 : {
342 0 : GNUNET_free (cp);
343 0 : GNUNET_break_op (0);
344 0 : return GNUNET_SYSERR;
345 : }
346 11 : if ((0 != strcasecmp ("taler",
347 11 : u.scheme)) &&
348 8 : (0 != strcasecmp ("taler+http",
349 8 : u.scheme)))
350 : {
351 1 : GNUNET_free (cp);
352 1 : GNUNET_break_op (0);
353 1 : return GNUNET_SYSERR;
354 : }
355 10 : parse_data->use_http = (0 == strcasecmp ("taler+http",
356 10 : u.scheme));
357 :
358 10 : if (0 != strcasecmp ("refund",
359 10 : u.host))
360 : {
361 1 : GNUNET_break_op (0);
362 1 : GNUNET_free (cp);
363 1 : return GNUNET_SYSERR;
364 : }
365 :
366 : {
367 : char *order_id;
368 9 : char *last_seg = strrchr (u.path,
369 : '/');
370 :
371 9 : if (NULL == last_seg)
372 : {
373 0 : GNUNET_break_op (0);
374 0 : GNUNET_free (cp);
375 0 : return GNUNET_SYSERR;
376 : }
377 9 : *last_seg = '\0';
378 9 : ++last_seg;
379 :
380 9 : order_id = strrchr (u.path,
381 : '/');
382 9 : if (NULL == order_id)
383 : {
384 1 : GNUNET_break_op (0);
385 1 : GNUNET_free (cp);
386 1 : return GNUNET_SYSERR;
387 : }
388 8 : *order_id = '\0';
389 8 : ++order_id;
390 8 : if (0 != strlen (last_seg))
391 : {
392 0 : GNUNET_break_op (0);
393 0 : GNUNET_free (cp);
394 0 : return GNUNET_SYSERR;
395 : }
396 :
397 : {
398 : char *mpp;
399 :
400 8 : mpp = strchr (u.path,
401 : '/');
402 8 : if (NULL != mpp)
403 : {
404 1 : *mpp = '\0';
405 1 : ++mpp;
406 : }
407 :
408 : parse_data->merchant_prefix_path
409 8 : = (NULL == mpp)
410 : ? NULL
411 8 : : GNUNET_strdup (mpp);
412 : }
413 8 : parse_data->merchant_host = GNUNET_strdup (u.path);
414 8 : parse_data->order_id = GNUNET_strdup (order_id);
415 : parse_data->ssid
416 16 : = (NULL == u.fragment)
417 : ? NULL
418 8 : : GNUNET_strdup (u.fragment);
419 : }
420 8 : GNUNET_free (cp);
421 8 : return GNUNET_OK;
422 : }
423 :
424 :
425 : void
426 8 : TALER_MERCHANT_parse_refund_uri_free (
427 : struct TALER_MERCHANT_RefundUriData *parse_data)
428 : {
429 8 : GNUNET_free (parse_data->merchant_host);
430 8 : GNUNET_free (parse_data->merchant_prefix_path);
431 8 : GNUNET_free (parse_data->order_id);
432 8 : GNUNET_free (parse_data->ssid);
433 8 : }
434 :
435 :
436 : void
437 69 : TALER_MERCHANT_handle_order_creation_response_ (
438 : TALER_MERCHANT_PostOrdersCallback cb,
439 : void *cb_cls,
440 : long response_code,
441 : const json_t *json)
442 : {
443 69 : struct TALER_MERCHANT_PostOrdersReply por = {
444 69 : .hr.http_status = (unsigned int) response_code,
445 : .hr.reply = json
446 : };
447 : struct TALER_ClaimTokenP token;
448 :
449 69 : switch (response_code)
450 : {
451 0 : case 0:
452 0 : por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
453 0 : break;
454 47 : case MHD_HTTP_OK:
455 : {
456 : bool no_token;
457 : bool no_pay_deadline;
458 : struct GNUNET_JSON_Specification spec[] = {
459 47 : GNUNET_JSON_spec_string ("order_id",
460 : &por.details.ok.order_id),
461 47 : GNUNET_JSON_spec_mark_optional (
462 : GNUNET_JSON_spec_fixed_auto ("token",
463 : &token),
464 : &no_token),
465 47 : GNUNET_JSON_spec_mark_optional (
466 : /* optional for pre-v21 compatibility */
467 : GNUNET_JSON_spec_timestamp ("pay_deadline",
468 : &por.details.ok.pay_deadline),
469 : &no_pay_deadline),
470 47 : GNUNET_JSON_spec_end ()
471 : };
472 :
473 47 : if (GNUNET_OK !=
474 47 : GNUNET_JSON_parse (json,
475 : spec,
476 : NULL, NULL))
477 : {
478 0 : GNUNET_break_op (0);
479 0 : por.hr.http_status = 0;
480 0 : por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
481 : }
482 : else
483 : {
484 47 : if (! no_token)
485 43 : por.details.ok.token = &token;
486 47 : if (no_pay_deadline)
487 : {
488 45 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
489 : "Talking to outdated merchant backend, payment deadline unavailable.\n");
490 45 : por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
491 : }
492 : }
493 : }
494 47 : break;
495 2 : case MHD_HTTP_BAD_REQUEST:
496 2 : json_dumpf (json,
497 : stderr,
498 : JSON_INDENT (2));
499 2 : por.hr.ec = TALER_JSON_get_error_code (json);
500 2 : por.hr.hint = TALER_JSON_get_error_hint (json);
501 : /* This should never happen, either us or
502 : the merchant is buggy (or API version conflict);
503 : just pass JSON reply to the application */
504 2 : break;
505 0 : case MHD_HTTP_UNAUTHORIZED:
506 0 : por.hr.ec = TALER_JSON_get_error_code (json);
507 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
508 : /* Nothing really to verify, merchant says we need to authenticate. */
509 0 : break;
510 0 : case MHD_HTTP_FORBIDDEN:
511 : /* Nothing really to verify, merchant says one
512 : of the signatures is invalid; as we checked them,
513 : this should never happen, we should pass the JSON
514 : reply to the application */
515 0 : por.hr.ec = TALER_JSON_get_error_code (json);
516 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
517 0 : break;
518 6 : case MHD_HTTP_NOT_FOUND:
519 : /* Nothing really to verify, this should never
520 : happen, we should pass the JSON reply to the application */
521 6 : por.hr.ec = TALER_JSON_get_error_code (json);
522 6 : por.hr.hint = TALER_JSON_get_error_hint (json);
523 6 : break;
524 12 : case MHD_HTTP_CONFLICT:
525 12 : por.hr.ec = TALER_JSON_get_error_code (json);
526 12 : por.hr.hint = TALER_JSON_get_error_hint (json);
527 12 : break;
528 2 : case MHD_HTTP_GONE:
529 : /* The quantity of some product requested was not available. */
530 : {
531 : bool rq_frac_missing;
532 : bool aq_frac_missing;
533 :
534 : struct GNUNET_JSON_Specification spec[] = {
535 2 : GNUNET_JSON_spec_string (
536 : "product_id",
537 : &por.details.gone.product_id),
538 2 : GNUNET_JSON_spec_uint64 (
539 : "requested_quantity",
540 : &por.details.gone.requested_quantity),
541 2 : GNUNET_JSON_spec_mark_optional (
542 : GNUNET_JSON_spec_uint32 (
543 : "requested_quantity_frac",
544 : &por.details.gone.requested_quantity_frac),
545 : &rq_frac_missing),
546 2 : GNUNET_JSON_spec_uint64 (
547 : "available_quantity",
548 : &por.details.gone.available_quantity),
549 2 : GNUNET_JSON_spec_mark_optional (
550 : GNUNET_JSON_spec_uint32 (
551 : "available_quantity_frac",
552 : &por.details.gone.available_quantity_frac),
553 : &aq_frac_missing),
554 2 : GNUNET_JSON_spec_mark_optional (
555 : GNUNET_JSON_spec_timestamp (
556 : "restock_expected",
557 : &por.details.gone.restock_expected),
558 : NULL),
559 2 : GNUNET_JSON_spec_end ()
560 : };
561 :
562 2 : if (GNUNET_OK !=
563 2 : GNUNET_JSON_parse (json,
564 : spec,
565 : NULL, NULL))
566 : {
567 0 : GNUNET_break_op (0);
568 0 : por.hr.http_status = 0;
569 0 : por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
570 : }
571 : else
572 : {
573 2 : if (rq_frac_missing)
574 2 : por.details.gone.requested_quantity_frac = 0;
575 2 : if (aq_frac_missing)
576 2 : por.details.gone.available_quantity_frac = 0;
577 : }
578 2 : break;
579 : }
580 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
581 : /* Order total too high for legal reasons */
582 0 : por.hr.ec = TALER_JSON_get_error_code (json);
583 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
584 0 : break;
585 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
586 : /* Server had an internal issue; we should retry,
587 : but this API leaves this to the application */
588 0 : por.hr.ec = TALER_JSON_get_error_code (json);
589 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
590 0 : break;
591 0 : default:
592 : /* unexpected response code */
593 0 : por.hr.ec = TALER_JSON_get_error_code (json);
594 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
595 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
596 : "Unexpected response code %u/%d\n",
597 : (unsigned int) response_code,
598 : (int) por.hr.ec);
599 0 : GNUNET_break_op (0);
600 0 : break;
601 : } // end of switch
602 69 : cb (cb_cls,
603 : &por);
604 69 : }
|