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 "taler/platform.h"
24 : #include "microhttpd.h"
25 : #include <curl/curl.h>
26 : #include "taler/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/taler_merchant_util.h"
31 :
32 :
33 : static void
34 0 : TALER_MERCHANT_format_fractional_string (uint64_t integer,
35 : uint32_t fractional,
36 : char *buffer,
37 : size_t buffer_length)
38 : {
39 0 : GNUNET_assert (NULL != buffer);
40 0 : GNUNET_assert (0 < buffer_length);
41 0 : GNUNET_assert (fractional < TALER_MERCHANT_UNIT_FRAC_BASE);
42 :
43 0 : if (0 == fractional)
44 : {
45 0 : GNUNET_snprintf (buffer,
46 : buffer_length,
47 : "%lu",
48 : integer);
49 0 : return;
50 : }
51 : {
52 : char frac_buf[TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS + 1];
53 : size_t idx;
54 :
55 0 : GNUNET_snprintf (frac_buf,
56 : sizeof (frac_buf),
57 : "%0*u",
58 : TALER_MERCHANT_UNIT_FRAC_MAX_DIGITS,
59 : (unsigned int) fractional);
60 0 : for (idx = strlen (frac_buf); idx > 0; idx--)
61 : {
62 0 : if ('0' != frac_buf[idx - 1])
63 0 : break;
64 0 : frac_buf[idx - 1] = '\0';
65 : }
66 0 : if ('\0' == frac_buf[0])
67 0 : GNUNET_strlcpy (frac_buf,
68 : "0",
69 : sizeof (frac_buf));
70 0 : GNUNET_snprintf (buffer,
71 : buffer_length,
72 : "%lu.%s",
73 : integer,
74 : frac_buf);
75 : }
76 : }
77 :
78 :
79 : void
80 0 : TALER_MERCHANT_format_quantity_string (uint64_t quantity,
81 : uint32_t quantity_frac,
82 : char *buffer,
83 : size_t buffer_length)
84 : {
85 0 : TALER_MERCHANT_format_fractional_string (quantity,
86 : quantity_frac,
87 : buffer,
88 : buffer_length);
89 0 : }
90 :
91 :
92 : void
93 0 : 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 0 : 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 0 : TALER_MERCHANT_format_fractional_string (total_stock,
107 : total_stock_frac,
108 : buffer,
109 : buffer_length);
110 : }
111 :
112 :
113 : void
114 0 : 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 0 : memset (hr, 0, sizeof (*hr));
121 0 : hr->reply = response;
122 0 : hr->http_status = http_status;
123 0 : if (NULL == response)
124 : {
125 0 : hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE;
126 0 : return;
127 : }
128 0 : hr->ec = TALER_JSON_get_error_code (response);
129 0 : hr->hint = TALER_JSON_get_error_hint (response);
130 :
131 : /* handle 'exchange_http_status' */
132 0 : 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 0 : if (NULL == jc)
137 0 : return; /* no need to bother with exchange_code/hint if we had no status */
138 0 : if (! json_is_integer (jc))
139 : {
140 0 : GNUNET_break_op (0);
141 0 : return;
142 : }
143 0 : hr->exchange_http_status = (unsigned int) json_integer_value (jc);
144 :
145 : /* handle 'exchange_reply' */
146 0 : jc = json_object_get (response,
147 : "exchange_reply");
148 0 : if (! json_is_object (jc))
149 : {
150 0 : GNUNET_break_op (0);
151 : }
152 : else
153 : {
154 0 : hr->exchange_reply = jc;
155 : }
156 :
157 : /* handle 'exchange_code' */
158 0 : 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 0 : if (NULL == jc)
163 0 : return; /* no need to bother with exchange-hint if we had no code */
164 0 : 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 0 : hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc);
172 : }
173 :
174 : /* handle 'exchange-hint' */
175 0 : 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 0 : if (NULL == jc)
180 0 : 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 7 : TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
194 : struct TALER_MERCHANT_PayUriData *parse_data)
195 : {
196 7 : char *cp = GNUNET_strdup (pay_uri);
197 : struct GNUNET_Uri u;
198 :
199 7 : if (0 !=
200 7 : 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 7 : if ((0 != strcasecmp ("taler",
208 7 : u.scheme)) &&
209 3 : (0 != strcasecmp ("taler+http",
210 3 : 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 6 : parse_data->use_http = (0 == strcasecmp ("taler+http",
220 6 : u.scheme));
221 6 : if (0 != strcasecmp ("pay",
222 6 : 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 5 : char *session_id = strrchr (u.path,
233 : '/');
234 5 : struct TALER_ClaimTokenP *claim_token = NULL;
235 :
236 5 : if (NULL == session_id)
237 : {
238 0 : GNUNET_break_op (0);
239 0 : GNUNET_free (cp);
240 0 : return GNUNET_SYSERR;
241 : }
242 5 : *session_id = '\0';
243 5 : ++session_id;
244 :
245 5 : order_id = strrchr (u.path,
246 : '/');
247 5 : if (NULL == order_id)
248 : {
249 1 : GNUNET_break_op (0);
250 1 : GNUNET_free (cp);
251 1 : return GNUNET_SYSERR;
252 : }
253 4 : *order_id = '\0';
254 4 : ++order_id;
255 4 : mpp = strchr (u.path,
256 : '/');
257 4 : if (NULL != mpp)
258 : {
259 1 : *mpp = '\0';
260 1 : ++mpp;
261 : }
262 :
263 : {
264 4 : char *ct_str = u.query;
265 : char *ct_data;
266 :
267 4 : if (NULL != ct_str)
268 : {
269 1 : ct_data = strchr (u.query,
270 : '=');
271 1 : if (NULL == ct_data)
272 : {
273 0 : GNUNET_break_op (0);
274 0 : GNUNET_free (cp);
275 0 : return GNUNET_SYSERR;
276 : }
277 1 : *ct_data = '\0';
278 1 : ++ct_data;
279 1 : claim_token = GNUNET_new (struct TALER_ClaimTokenP);
280 1 : if ( (0 != strcmp ("c",
281 2 : u.query)) ||
282 : (GNUNET_OK !=
283 1 : 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 4 : = (NULL == mpp)
298 : ? NULL
299 4 : : GNUNET_strdup (mpp);
300 4 : parse_data->merchant_host = GNUNET_strdup (u.path);
301 4 : parse_data->order_id = GNUNET_strdup (order_id);
302 : parse_data->session_id
303 8 : = (0 < strlen (session_id))
304 2 : ? GNUNET_strdup (session_id)
305 4 : : NULL;
306 4 : parse_data->claim_token = claim_token;
307 : parse_data->ssid
308 8 : = (NULL == u.fragment)
309 : ? NULL
310 4 : : GNUNET_strdup (u.fragment);
311 : }
312 4 : GNUNET_free (cp);
313 4 : return GNUNET_OK;
314 : }
315 :
316 :
317 : void
318 4 : TALER_MERCHANT_parse_pay_uri_free (
319 : struct TALER_MERCHANT_PayUriData *parse_data)
320 : {
321 4 : GNUNET_free (parse_data->merchant_host);
322 4 : GNUNET_free (parse_data->merchant_prefix_path);
323 4 : GNUNET_free (parse_data->order_id);
324 4 : GNUNET_free (parse_data->session_id);
325 4 : GNUNET_free (parse_data->claim_token);
326 4 : GNUNET_free (parse_data->ssid);
327 4 : }
328 :
329 :
330 : enum GNUNET_GenericReturnValue
331 5 : TALER_MERCHANT_parse_refund_uri (
332 : const char *refund_uri,
333 : struct TALER_MERCHANT_RefundUriData *parse_data)
334 : {
335 5 : char *cp = GNUNET_strdup (refund_uri);
336 : struct GNUNET_Uri u;
337 :
338 5 : if (0 !=
339 5 : 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 5 : if ((0 != strcasecmp ("taler",
347 5 : u.scheme)) &&
348 2 : (0 != strcasecmp ("taler+http",
349 2 : u.scheme)))
350 : {
351 1 : GNUNET_free (cp);
352 1 : GNUNET_break_op (0);
353 1 : return GNUNET_SYSERR;
354 : }
355 4 : parse_data->use_http = (0 == strcasecmp ("taler+http",
356 4 : u.scheme));
357 :
358 4 : if (0 != strcasecmp ("refund",
359 4 : 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 3 : char *last_seg = strrchr (u.path,
369 : '/');
370 :
371 3 : if (NULL == last_seg)
372 : {
373 0 : GNUNET_break_op (0);
374 0 : GNUNET_free (cp);
375 0 : return GNUNET_SYSERR;
376 : }
377 3 : *last_seg = '\0';
378 3 : ++last_seg;
379 :
380 3 : order_id = strrchr (u.path,
381 : '/');
382 3 : if (NULL == order_id)
383 : {
384 1 : GNUNET_break_op (0);
385 1 : GNUNET_free (cp);
386 1 : return GNUNET_SYSERR;
387 : }
388 2 : *order_id = '\0';
389 2 : ++order_id;
390 2 : 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 2 : mpp = strchr (u.path,
401 : '/');
402 2 : if (NULL != mpp)
403 : {
404 1 : *mpp = '\0';
405 1 : ++mpp;
406 : }
407 :
408 : parse_data->merchant_prefix_path
409 2 : = (NULL == mpp)
410 : ? NULL
411 2 : : GNUNET_strdup (mpp);
412 : }
413 2 : parse_data->merchant_host = GNUNET_strdup (u.path);
414 2 : parse_data->order_id = GNUNET_strdup (order_id);
415 : parse_data->ssid
416 4 : = (NULL == u.fragment)
417 : ? NULL
418 2 : : GNUNET_strdup (u.fragment);
419 : }
420 2 : GNUNET_free (cp);
421 2 : return GNUNET_OK;
422 : }
423 :
424 :
425 : void
426 2 : TALER_MERCHANT_parse_refund_uri_free (
427 : struct TALER_MERCHANT_RefundUriData *parse_data)
428 : {
429 2 : GNUNET_free (parse_data->merchant_host);
430 2 : GNUNET_free (parse_data->merchant_prefix_path);
431 2 : GNUNET_free (parse_data->order_id);
432 2 : GNUNET_free (parse_data->ssid);
433 2 : }
434 :
435 :
436 : void
437 0 : TALER_MERCHANT_handle_order_creation_response_ (
438 : TALER_MERCHANT_PostPrivateOrdersCallback cb,
439 : void *cb_cls,
440 : long response_code,
441 : const json_t *json)
442 : {
443 0 : struct TALER_MERCHANT_PostPrivateOrdersResponse por = {
444 0 : .hr.http_status = (unsigned int) response_code,
445 : .hr.reply = json
446 : };
447 : struct TALER_ClaimTokenP token;
448 :
449 0 : switch (response_code)
450 : {
451 0 : case 0:
452 0 : por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
453 0 : break;
454 0 : case MHD_HTTP_OK:
455 : {
456 : bool no_token;
457 : bool no_pay_deadline;
458 : struct GNUNET_JSON_Specification spec[] = {
459 0 : GNUNET_JSON_spec_string ("order_id",
460 : &por.details.ok.order_id),
461 0 : GNUNET_JSON_spec_mark_optional (
462 : GNUNET_JSON_spec_fixed_auto ("token",
463 : &token),
464 : &no_token),
465 0 : 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 0 : GNUNET_JSON_spec_end ()
471 : };
472 :
473 0 : if (GNUNET_OK !=
474 0 : 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 0 : if (! no_token)
485 0 : por.details.ok.token = &token;
486 0 : if (no_pay_deadline)
487 : {
488 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
489 : "Talking to outdated merchant backend, payment deadline unavailable.\n");
490 0 : por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
491 : }
492 : }
493 : }
494 0 : break;
495 0 : case MHD_HTTP_BAD_REQUEST:
496 0 : json_dumpf (json,
497 : stderr,
498 : JSON_INDENT (2));
499 0 : por.hr.ec = TALER_JSON_get_error_code (json);
500 0 : 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 0 : 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 0 : 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 0 : por.hr.ec = TALER_JSON_get_error_code (json);
522 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
523 0 : break;
524 0 : case MHD_HTTP_CONFLICT:
525 0 : por.hr.ec = TALER_JSON_get_error_code (json);
526 0 : por.hr.hint = TALER_JSON_get_error_hint (json);
527 0 : break;
528 0 : 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 0 : GNUNET_JSON_spec_string (
536 : "product_id",
537 : &por.details.gone.product_id),
538 0 : GNUNET_JSON_spec_uint64 (
539 : "requested_quantity",
540 : &por.details.gone.requested_quantity),
541 0 : 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 0 : GNUNET_JSON_spec_uint64 (
547 : "available_quantity",
548 : &por.details.gone.available_quantity),
549 0 : 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 0 : GNUNET_JSON_spec_mark_optional (
555 : GNUNET_JSON_spec_timestamp (
556 : "restock_expected",
557 : &por.details.gone.restock_expected),
558 : NULL),
559 0 : GNUNET_JSON_spec_end ()
560 : };
561 :
562 0 : if (GNUNET_OK !=
563 0 : 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 0 : if (rq_frac_missing)
574 0 : por.details.gone.requested_quantity_frac = 0;
575 0 : if (aq_frac_missing)
576 0 : por.details.gone.available_quantity_frac = 0;
577 : }
578 0 : 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 0 : cb (cb_cls,
603 : &por);
604 0 : }
|