merchant_api_common.c (16834B)
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 TALER_MERCHANT_format_fractional_string (uint64_t integer, 35 uint32_t fractional, 36 char *buffer, 37 size_t buffer_length) 38 { 39 GNUNET_assert (NULL != buffer); 40 GNUNET_assert (0 < buffer_length); 41 GNUNET_assert (fractional < MERCHANT_UNIT_FRAC_BASE); 42 43 if (0 == fractional) 44 { 45 GNUNET_snprintf (buffer, 46 buffer_length, 47 "%lu", 48 integer); 49 return; 50 } 51 { 52 char frac_buf[MERCHANT_UNIT_FRAC_MAX_DIGITS + 1]; 53 size_t idx; 54 55 GNUNET_snprintf (frac_buf, 56 sizeof (frac_buf), 57 "%0*u", 58 MERCHANT_UNIT_FRAC_MAX_DIGITS, 59 (unsigned int) fractional); 60 for (idx = strlen (frac_buf); idx > 0; idx--) 61 { 62 if ('0' != frac_buf[idx - 1]) 63 break; 64 frac_buf[idx - 1] = '\0'; 65 } 66 if ('\0' == frac_buf[0]) 67 GNUNET_strlcpy (frac_buf, 68 "0", 69 sizeof (frac_buf)); 70 GNUNET_snprintf (buffer, 71 buffer_length, 72 "%lu.%s", 73 integer, 74 frac_buf); 75 } 76 } 77 78 79 void 80 TALER_MERCHANT_format_quantity_string (uint64_t quantity, 81 uint32_t quantity_frac, 82 char *buffer, 83 size_t buffer_length) 84 { 85 TALER_MERCHANT_format_fractional_string (quantity, 86 quantity_frac, 87 buffer, 88 buffer_length); 89 } 90 91 92 void 93 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 if ( (INT64_MAX == (int64_t) total_stock) && 99 (INT32_MAX == (int32_t) total_stock_frac) ) 100 { 101 GNUNET_snprintf (buffer, 102 buffer_length, 103 "-1"); 104 return; 105 } 106 TALER_MERCHANT_format_fractional_string (total_stock, 107 total_stock_frac, 108 buffer, 109 buffer_length); 110 } 111 112 113 void 114 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 memset (hr, 0, sizeof (*hr)); 121 hr->reply = response; 122 hr->http_status = http_status; 123 if (NULL == response) 124 { 125 hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE; 126 return; 127 } 128 hr->ec = TALER_JSON_get_error_code (response); 129 hr->hint = TALER_JSON_get_error_hint (response); 130 131 /* handle 'exchange_http_status' */ 132 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 if (NULL == jc) 137 return; /* no need to bother with exchange_code/hint if we had no status */ 138 if (! json_is_integer (jc)) 139 { 140 GNUNET_break_op (0); 141 return; 142 } 143 hr->exchange_http_status = (unsigned int) json_integer_value (jc); 144 145 /* handle 'exchange_reply' */ 146 jc = json_object_get (response, 147 "exchange_reply"); 148 if (! json_is_object (jc)) 149 { 150 GNUNET_break_op (0); 151 } 152 else 153 { 154 hr->exchange_reply = jc; 155 } 156 157 /* handle 'exchange_code' */ 158 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 if (NULL == jc) 163 return; /* no need to bother with exchange-hint if we had no code */ 164 if (! json_is_integer (jc)) 165 { 166 GNUNET_break_op (0); 167 hr->exchange_code = TALER_EC_INVALID; 168 } 169 else 170 { 171 hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc); 172 } 173 174 /* handle 'exchange-hint' */ 175 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 if (NULL == jc) 180 return; 181 if (! json_is_string (jc)) 182 { 183 GNUNET_break_op (0); 184 } 185 else 186 { 187 hr->exchange_hint = json_string_value (jc); 188 } 189 } 190 191 192 enum GNUNET_GenericReturnValue 193 TALER_MERCHANT_parse_pay_uri (const char *pay_uri, 194 struct TALER_MERCHANT_PayUriData *parse_data) 195 { 196 char *cp = GNUNET_strdup (pay_uri); 197 struct GNUNET_Uri u; 198 199 if (0 != 200 GNUNET_uri_parse (&u, 201 cp)) 202 { 203 GNUNET_free (cp); 204 GNUNET_break_op (0); 205 return GNUNET_SYSERR; 206 } 207 if ((0 != strcasecmp ("taler", 208 u.scheme)) && 209 (0 != strcasecmp ("taler+http", 210 u.scheme))) 211 { 212 fprintf (stderr, 213 "Bad schema %s\n", 214 u.scheme); 215 GNUNET_free (cp); 216 GNUNET_break_op (0); 217 return GNUNET_SYSERR; 218 } 219 parse_data->use_http = (0 == strcasecmp ("taler+http", 220 u.scheme)); 221 if (0 != strcasecmp ("pay", 222 u.host)) 223 { 224 GNUNET_break_op (0); 225 GNUNET_free (cp); 226 return GNUNET_SYSERR; 227 } 228 229 { 230 char *order_id; 231 char *mpp; 232 char *session_id = strrchr (u.path, 233 '/'); 234 struct TALER_ClaimTokenP *claim_token = NULL; 235 236 if (NULL == session_id) 237 { 238 GNUNET_break_op (0); 239 GNUNET_free (cp); 240 return GNUNET_SYSERR; 241 } 242 *session_id = '\0'; 243 ++session_id; 244 245 order_id = strrchr (u.path, 246 '/'); 247 if (NULL == order_id) 248 { 249 GNUNET_break_op (0); 250 GNUNET_free (cp); 251 return GNUNET_SYSERR; 252 } 253 *order_id = '\0'; 254 ++order_id; 255 mpp = strchr (u.path, 256 '/'); 257 if (NULL != mpp) 258 { 259 *mpp = '\0'; 260 ++mpp; 261 } 262 263 { 264 char *ct_str = u.query; 265 char *ct_data; 266 267 if (NULL != ct_str) 268 { 269 ct_data = strchr (u.query, 270 '='); 271 if (NULL == ct_data) 272 { 273 GNUNET_break_op (0); 274 GNUNET_free (cp); 275 return GNUNET_SYSERR; 276 } 277 *ct_data = '\0'; 278 ++ct_data; 279 claim_token = GNUNET_new (struct TALER_ClaimTokenP); 280 if ( (0 != strcmp ("c", 281 u.query)) || 282 (GNUNET_OK != 283 GNUNET_STRINGS_string_to_data (ct_data, 284 strlen (ct_data), 285 claim_token, 286 sizeof (*claim_token))) ) 287 { 288 GNUNET_break_op (0); 289 GNUNET_free (claim_token); 290 GNUNET_free (cp); 291 return GNUNET_SYSERR; 292 } 293 } 294 } 295 296 parse_data->merchant_prefix_path 297 = (NULL == mpp) 298 ? NULL 299 : GNUNET_strdup (mpp); 300 parse_data->merchant_host = GNUNET_strdup (u.path); 301 parse_data->order_id = GNUNET_strdup (order_id); 302 parse_data->session_id 303 = (0 < strlen (session_id)) 304 ? GNUNET_strdup (session_id) 305 : NULL; 306 parse_data->claim_token = claim_token; 307 parse_data->ssid 308 = (NULL == u.fragment) 309 ? NULL 310 : GNUNET_strdup (u.fragment); 311 } 312 GNUNET_free (cp); 313 return GNUNET_OK; 314 } 315 316 317 void 318 TALER_MERCHANT_parse_pay_uri_free ( 319 struct TALER_MERCHANT_PayUriData *parse_data) 320 { 321 GNUNET_free (parse_data->merchant_host); 322 GNUNET_free (parse_data->merchant_prefix_path); 323 GNUNET_free (parse_data->order_id); 324 GNUNET_free (parse_data->session_id); 325 GNUNET_free (parse_data->claim_token); 326 GNUNET_free (parse_data->ssid); 327 } 328 329 330 enum GNUNET_GenericReturnValue 331 TALER_MERCHANT_parse_refund_uri ( 332 const char *refund_uri, 333 struct TALER_MERCHANT_RefundUriData *parse_data) 334 { 335 char *cp = GNUNET_strdup (refund_uri); 336 struct GNUNET_Uri u; 337 338 if (0 != 339 GNUNET_uri_parse (&u, 340 cp)) 341 { 342 GNUNET_free (cp); 343 GNUNET_break_op (0); 344 return GNUNET_SYSERR; 345 } 346 if ((0 != strcasecmp ("taler", 347 u.scheme)) && 348 (0 != strcasecmp ("taler+http", 349 u.scheme))) 350 { 351 GNUNET_free (cp); 352 GNUNET_break_op (0); 353 return GNUNET_SYSERR; 354 } 355 parse_data->use_http = (0 == strcasecmp ("taler+http", 356 u.scheme)); 357 358 if (0 != strcasecmp ("refund", 359 u.host)) 360 { 361 GNUNET_break_op (0); 362 GNUNET_free (cp); 363 return GNUNET_SYSERR; 364 } 365 366 { 367 char *order_id; 368 char *last_seg = strrchr (u.path, 369 '/'); 370 371 if (NULL == last_seg) 372 { 373 GNUNET_break_op (0); 374 GNUNET_free (cp); 375 return GNUNET_SYSERR; 376 } 377 *last_seg = '\0'; 378 ++last_seg; 379 380 order_id = strrchr (u.path, 381 '/'); 382 if (NULL == order_id) 383 { 384 GNUNET_break_op (0); 385 GNUNET_free (cp); 386 return GNUNET_SYSERR; 387 } 388 *order_id = '\0'; 389 ++order_id; 390 if (0 != strlen (last_seg)) 391 { 392 GNUNET_break_op (0); 393 GNUNET_free (cp); 394 return GNUNET_SYSERR; 395 } 396 397 { 398 char *mpp; 399 400 mpp = strchr (u.path, 401 '/'); 402 if (NULL != mpp) 403 { 404 *mpp = '\0'; 405 ++mpp; 406 } 407 408 parse_data->merchant_prefix_path 409 = (NULL == mpp) 410 ? NULL 411 : GNUNET_strdup (mpp); 412 } 413 parse_data->merchant_host = GNUNET_strdup (u.path); 414 parse_data->order_id = GNUNET_strdup (order_id); 415 parse_data->ssid 416 = (NULL == u.fragment) 417 ? NULL 418 : GNUNET_strdup (u.fragment); 419 } 420 GNUNET_free (cp); 421 return GNUNET_OK; 422 } 423 424 425 void 426 TALER_MERCHANT_parse_refund_uri_free ( 427 struct TALER_MERCHANT_RefundUriData *parse_data) 428 { 429 GNUNET_free (parse_data->merchant_host); 430 GNUNET_free (parse_data->merchant_prefix_path); 431 GNUNET_free (parse_data->order_id); 432 GNUNET_free (parse_data->ssid); 433 } 434 435 436 void 437 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 struct TALER_MERCHANT_PostOrdersReply por = { 444 .hr.http_status = (unsigned int) response_code, 445 .hr.reply = json 446 }; 447 struct TALER_ClaimTokenP token; 448 449 switch (response_code) 450 { 451 case 0: 452 por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 453 break; 454 case MHD_HTTP_OK: 455 { 456 bool no_token; 457 bool no_pay_deadline; 458 struct GNUNET_JSON_Specification spec[] = { 459 GNUNET_JSON_spec_string ("order_id", 460 &por.details.ok.order_id), 461 GNUNET_JSON_spec_mark_optional ( 462 GNUNET_JSON_spec_fixed_auto ("token", 463 &token), 464 &no_token), 465 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 GNUNET_JSON_spec_end () 471 }; 472 473 if (GNUNET_OK != 474 GNUNET_JSON_parse (json, 475 spec, 476 NULL, NULL)) 477 { 478 GNUNET_break_op (0); 479 por.hr.http_status = 0; 480 por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 481 } 482 else 483 { 484 if (! no_token) 485 por.details.ok.token = &token; 486 if (no_pay_deadline) 487 { 488 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 489 "Talking to outdated merchant backend, payment deadline unavailable.\n"); 490 por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS; 491 } 492 } 493 } 494 break; 495 case MHD_HTTP_BAD_REQUEST: 496 json_dumpf (json, 497 stderr, 498 JSON_INDENT (2)); 499 por.hr.ec = TALER_JSON_get_error_code (json); 500 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 break; 505 case MHD_HTTP_UNAUTHORIZED: 506 por.hr.ec = TALER_JSON_get_error_code (json); 507 por.hr.hint = TALER_JSON_get_error_hint (json); 508 /* Nothing really to verify, merchant says we need to authenticate. */ 509 break; 510 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 por.hr.ec = TALER_JSON_get_error_code (json); 516 por.hr.hint = TALER_JSON_get_error_hint (json); 517 break; 518 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 por.hr.ec = TALER_JSON_get_error_code (json); 522 por.hr.hint = TALER_JSON_get_error_hint (json); 523 break; 524 case MHD_HTTP_CONFLICT: 525 por.hr.ec = TALER_JSON_get_error_code (json); 526 por.hr.hint = TALER_JSON_get_error_hint (json); 527 break; 528 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 GNUNET_JSON_spec_string ( 536 "product_id", 537 &por.details.gone.product_id), 538 GNUNET_JSON_spec_uint64 ( 539 "requested_quantity", 540 &por.details.gone.requested_quantity), 541 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 GNUNET_JSON_spec_uint64 ( 547 "available_quantity", 548 &por.details.gone.available_quantity), 549 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 GNUNET_JSON_spec_mark_optional ( 555 GNUNET_JSON_spec_timestamp ( 556 "restock_expected", 557 &por.details.gone.restock_expected), 558 NULL), 559 GNUNET_JSON_spec_end () 560 }; 561 562 if (GNUNET_OK != 563 GNUNET_JSON_parse (json, 564 spec, 565 NULL, NULL)) 566 { 567 GNUNET_break_op (0); 568 por.hr.http_status = 0; 569 por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 570 } 571 else 572 { 573 if (rq_frac_missing) 574 por.details.gone.requested_quantity_frac = 0; 575 if (aq_frac_missing) 576 por.details.gone.available_quantity_frac = 0; 577 } 578 break; 579 } 580 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 581 /* Order total too high for legal reasons */ 582 por.hr.ec = TALER_JSON_get_error_code (json); 583 por.hr.hint = TALER_JSON_get_error_hint (json); 584 break; 585 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 por.hr.ec = TALER_JSON_get_error_code (json); 589 por.hr.hint = TALER_JSON_get_error_hint (json); 590 break; 591 default: 592 /* unexpected response code */ 593 por.hr.ec = TALER_JSON_get_error_code (json); 594 por.hr.hint = TALER_JSON_get_error_hint (json); 595 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 596 "Unexpected response code %u/%d\n", 597 (unsigned int) response_code, 598 (int) por.hr.ec); 599 GNUNET_break_op (0); 600 break; 601 } // end of switch 602 cb (cb_cls, 603 &por); 604 }