merchant_api_post-private-orders.c (17599B)
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 * Challenge copied from the options, if present. 120 */ 121 struct TALER_PosChallengeP challenge; 122 123 bool have_challenge; 124 125 /** 126 * Optional inventory products. 127 */ 128 const struct TALER_MERCHANT_PostPrivateOrdersInventoryProduct * 129 inventory_products; 130 131 /** 132 * Number of inventory products. 133 */ 134 unsigned int num_inventory_products; 135 136 /** 137 * Optional lock UUIDs. 138 */ 139 const char **lock_uuids; 140 141 /** 142 * Number of lock UUIDs. 143 */ 144 unsigned int num_lock_uuids; 145 }; 146 147 148 /** 149 * Function called when we're done processing the 150 * HTTP POST /private/orders request. 151 * 152 * @param cls the `struct TALER_MERCHANT_PostPrivateOrdersHandle` 153 * @param response_code HTTP response code, 0 on error 154 * @param response response body, NULL if not in JSON 155 */ 156 static void 157 handle_post_orders_finished (void *cls, 158 long response_code, 159 const void *response) 160 { 161 struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh = cls; 162 const json_t *json = response; 163 struct TALER_MERCHANT_PostPrivateOrdersResponse por = { 164 .hr.http_status = (unsigned int) response_code, 165 .hr.reply = json 166 }; 167 struct TALER_ClaimTokenP token; 168 169 ppoh->job = NULL; 170 switch (response_code) 171 { 172 case 0: 173 por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 174 break; 175 case MHD_HTTP_OK: 176 { 177 bool no_token; 178 bool no_pay_deadline; 179 struct GNUNET_JSON_Specification spec[] = { 180 TALER_JSON_spec_slug ("order_id", 181 &por.details.ok.order_id), 182 GNUNET_JSON_spec_mark_optional ( 183 GNUNET_JSON_spec_fixed_auto ("token", 184 &token), 185 &no_token), 186 GNUNET_JSON_spec_mark_optional ( 187 GNUNET_JSON_spec_timestamp ("pay_deadline", 188 &por.details.ok.pay_deadline), 189 &no_pay_deadline), 190 GNUNET_JSON_spec_end () 191 }; 192 193 if (GNUNET_OK != 194 GNUNET_JSON_parse (json, 195 spec, 196 NULL, NULL)) 197 { 198 GNUNET_break_op (0); 199 por.hr.http_status = 0; 200 por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 201 break; 202 } 203 if (! no_token) 204 por.details.ok.token = &token; 205 if (no_pay_deadline) 206 por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS; 207 break; 208 } 209 case MHD_HTTP_BAD_REQUEST: 210 por.hr.ec = TALER_JSON_get_error_code (json); 211 por.hr.hint = TALER_JSON_get_error_hint (json); 212 break; 213 case MHD_HTTP_UNAUTHORIZED: 214 por.hr.ec = TALER_JSON_get_error_code (json); 215 por.hr.hint = TALER_JSON_get_error_hint (json); 216 break; 217 case MHD_HTTP_FORBIDDEN: 218 por.hr.ec = TALER_JSON_get_error_code (json); 219 por.hr.hint = TALER_JSON_get_error_hint (json); 220 break; 221 case MHD_HTTP_NOT_FOUND: 222 por.hr.ec = TALER_JSON_get_error_code (json); 223 por.hr.hint = TALER_JSON_get_error_hint (json); 224 break; 225 case MHD_HTTP_CONFLICT: 226 por.hr.ec = TALER_JSON_get_error_code (json); 227 por.hr.hint = TALER_JSON_get_error_hint (json); 228 break; 229 case MHD_HTTP_GONE: 230 { 231 bool rq_frac_missing; 232 bool aq_frac_missing; 233 struct GNUNET_JSON_Specification spec[] = { 234 TALER_JSON_spec_slug ( 235 "product_id", 236 &por.details.gone.product_id), 237 GNUNET_JSON_spec_uint64 ( 238 "requested_quantity", 239 &por.details.gone.requested_quantity), 240 GNUNET_JSON_spec_mark_optional ( 241 GNUNET_JSON_spec_uint32 ( 242 "requested_quantity_frac", 243 &por.details.gone.requested_quantity_frac), 244 &rq_frac_missing), 245 GNUNET_JSON_spec_uint64 ( 246 "available_quantity", 247 &por.details.gone.available_quantity), 248 GNUNET_JSON_spec_mark_optional ( 249 GNUNET_JSON_spec_uint32 ( 250 "available_quantity_frac", 251 &por.details.gone.available_quantity_frac), 252 &aq_frac_missing), 253 GNUNET_JSON_spec_mark_optional ( 254 GNUNET_JSON_spec_string ( 255 "unit_requested_quantity", 256 &por.details.gone.unit_requested_quantity), 257 NULL), 258 GNUNET_JSON_spec_mark_optional ( 259 GNUNET_JSON_spec_string ( 260 "unit_available_quantity", 261 &por.details.gone.unit_available_quantity), 262 NULL), 263 GNUNET_JSON_spec_mark_optional ( 264 GNUNET_JSON_spec_timestamp ( 265 "restock_expected", 266 &por.details.gone.restock_expected), 267 NULL), 268 GNUNET_JSON_spec_end () 269 }; 270 271 if (GNUNET_OK != 272 GNUNET_JSON_parse (json, 273 spec, 274 NULL, NULL)) 275 { 276 GNUNET_break_op (0); 277 por.hr.http_status = 0; 278 por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 279 } 280 else 281 { 282 if (rq_frac_missing) 283 por.details.gone.requested_quantity_frac = 0; 284 if (aq_frac_missing) 285 por.details.gone.available_quantity_frac = 0; 286 } 287 break; 288 } 289 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 290 { 291 const json_t *jer = NULL; 292 293 por.hr.ec = TALER_JSON_get_error_code (json); 294 por.hr.hint = TALER_JSON_get_error_hint (json); 295 jer = json_object_get (json, 296 "exchange_rejections"); 297 if ( (NULL != jer) && 298 json_is_array (jer) ) 299 { 300 unsigned int rej_len = (unsigned int) json_array_size (jer); 301 302 if ( (json_array_size (jer) == (size_t) rej_len) && 303 (rej_len <= MAX_EXCHANGE_REJECTIONS) ) 304 { 305 struct TALER_MERCHANT_ExchangeRejectionDetail rejs[ 306 GNUNET_NZL (rej_len)]; 307 bool ok = true; 308 309 memset (rejs, 0, sizeof (rejs)); 310 for (unsigned int i = 0; i < rej_len; i++) 311 { 312 struct GNUNET_JSON_Specification rspec[] = { 313 TALER_JSON_spec_web_url ( 314 "exchange_url", 315 &rejs[i].exchange_url), 316 TALER_JSON_spec_ec ( 317 "code", 318 &rejs[i].code), 319 GNUNET_JSON_spec_mark_optional ( 320 GNUNET_JSON_spec_string ( 321 "hint", 322 &rejs[i].hint), 323 NULL), 324 GNUNET_JSON_spec_end () 325 }; 326 327 if (GNUNET_OK != 328 GNUNET_JSON_parse (json_array_get (jer, i), 329 rspec, 330 NULL, NULL)) 331 { 332 GNUNET_break_op (0); 333 ok = false; 334 break; 335 } 336 } 337 if (ok) 338 { 339 por.details.unavailable_for_legal_reasons 340 .num_exchange_rejections = rej_len; 341 por.details.unavailable_for_legal_reasons 342 .exchange_rejections = rejs; 343 ppoh->cb (ppoh->cb_cls, 344 &por); 345 TALER_MERCHANT_post_private_orders_cancel (ppoh); 346 return; 347 } 348 } 349 } 350 break; 351 } 352 case MHD_HTTP_INTERNAL_SERVER_ERROR: 353 por.hr.ec = TALER_JSON_get_error_code (json); 354 por.hr.hint = TALER_JSON_get_error_hint (json); 355 break; 356 default: 357 por.hr.ec = TALER_JSON_get_error_code (json); 358 por.hr.hint = TALER_JSON_get_error_hint (json); 359 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 360 "Unexpected response code %u/%d\n", 361 (unsigned int) response_code, 362 (int) por.hr.ec); 363 GNUNET_break_op (0); 364 break; 365 } 366 ppoh->cb (ppoh->cb_cls, 367 &por); 368 TALER_MERCHANT_post_private_orders_cancel (ppoh); 369 } 370 371 372 struct TALER_MERCHANT_PostPrivateOrdersHandle * 373 TALER_MERCHANT_post_private_orders_create ( 374 struct GNUNET_CURL_Context *ctx, 375 const char *url, 376 const json_t *order) 377 { 378 struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh; 379 380 ppoh = GNUNET_new (struct TALER_MERCHANT_PostPrivateOrdersHandle); 381 ppoh->ctx = ctx; 382 ppoh->base_url = GNUNET_strdup (url); 383 ppoh->order = json_incref ((json_t *) order); 384 ppoh->create_token = true; 385 return ppoh; 386 } 387 388 389 enum GNUNET_GenericReturnValue 390 TALER_MERCHANT_post_private_orders_set_options_ ( 391 struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh, 392 unsigned int num_options, 393 const struct TALER_MERCHANT_PostPrivateOrdersOptionValue *options) 394 { 395 for (unsigned int i = 0; i < num_options; i++) 396 { 397 switch (options[i].option) 398 { 399 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_END: 400 return GNUNET_OK; 401 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_REFUND_DELAY: 402 ppoh->refund_delay = options[i].details.refund_delay; 403 ppoh->refund_delay_set = true; 404 break; 405 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_PAYMENT_TARGET: 406 ppoh->payment_target = options[i].details.payment_target; 407 break; 408 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_SESSION_ID: 409 ppoh->session_id = options[i].details.session_id; 410 break; 411 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_CREATE_TOKEN: 412 ppoh->create_token = options[i].details.create_token; 413 break; 414 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_OTP_ID: 415 ppoh->otp_id = options[i].details.otp_id; 416 break; 417 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_CHALLENGE: 418 ppoh->have_challenge = (NULL != options[i].details.challenge); 419 if (ppoh->have_challenge) 420 ppoh->challenge = *options[i].details.challenge; 421 break; 422 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_INVENTORY_PRODUCTS: 423 ppoh->num_inventory_products 424 = options[i].details.inventory_products.num; 425 ppoh->inventory_products 426 = options[i].details.inventory_products.products; 427 break; 428 case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_LOCK_UUIDS: 429 ppoh->num_lock_uuids = options[i].details.lock_uuids.num; 430 ppoh->lock_uuids = options[i].details.lock_uuids.uuids; 431 break; 432 default: 433 GNUNET_break (0); 434 return GNUNET_SYSERR; 435 } 436 } 437 return GNUNET_OK; 438 } 439 440 441 enum TALER_ErrorCode 442 TALER_MERCHANT_post_private_orders_start ( 443 struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh, 444 TALER_MERCHANT_PostPrivateOrdersCallback cb, 445 TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls) 446 { 447 json_t *req; 448 CURL *eh; 449 450 ppoh->cb = cb; 451 ppoh->cb_cls = cb_cls; 452 ppoh->url = TALER_url_join (ppoh->base_url, 453 "private/orders", 454 NULL); 455 if (NULL == ppoh->url) 456 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 457 req = GNUNET_JSON_PACK ( 458 GNUNET_JSON_pack_object_incref ("order", 459 ppoh->order), 460 GNUNET_JSON_pack_allow_null ( 461 GNUNET_JSON_pack_string ("session_id", 462 ppoh->session_id)), 463 GNUNET_JSON_pack_allow_null ( 464 GNUNET_JSON_pack_string ("payment_target", 465 ppoh->payment_target)), 466 GNUNET_JSON_pack_allow_null ( 467 GNUNET_JSON_pack_string ("otp_id", 468 ppoh->otp_id))); 469 if (ppoh->have_challenge) 470 GNUNET_assert (0 == 471 json_object_set_new ( 472 req, 473 "challenge", 474 GNUNET_JSON_from_data_auto (&ppoh->challenge))); 475 if (ppoh->refund_delay_set && 476 (0 != ppoh->refund_delay.rel_value_us)) 477 { 478 GNUNET_assert (0 == 479 json_object_set_new (req, 480 "refund_delay", 481 GNUNET_JSON_from_time_rel ( 482 ppoh->refund_delay))); 483 } 484 if (0 != ppoh->num_inventory_products) 485 { 486 json_t *ipa = json_array (); 487 488 GNUNET_assert (NULL != ipa); 489 for (unsigned int i = 0; i < ppoh->num_inventory_products; i++) 490 { 491 json_t *ip; 492 493 { 494 char unit_quantity_buf[64]; 495 496 TALER_MERCHANT_format_quantity_string ( 497 ppoh->inventory_products[i].quantity, 498 ppoh->inventory_products[i].use_fractional_quantity 499 ? ppoh->inventory_products[i].quantity_frac 500 : 0, 501 unit_quantity_buf, 502 sizeof (unit_quantity_buf)); 503 ip = GNUNET_JSON_PACK ( 504 GNUNET_JSON_pack_string ("product_id", 505 ppoh->inventory_products[i].product_id), 506 GNUNET_JSON_pack_string ("unit_quantity", 507 unit_quantity_buf)); 508 } 509 if (ppoh->inventory_products[i].product_money_pot > 0) 510 { 511 GNUNET_assert ( 512 0 == 513 json_object_set_new ( 514 ip, 515 "product_money_pot", 516 json_integer ( 517 ppoh->inventory_products[i].product_money_pot))); 518 } 519 GNUNET_assert (0 == 520 json_array_append_new (ipa, 521 ip)); 522 } 523 GNUNET_assert (0 == 524 json_object_set_new (req, 525 "inventory_products", 526 ipa)); 527 } 528 if (0 != ppoh->num_lock_uuids) 529 { 530 json_t *ua = json_array (); 531 532 GNUNET_assert (NULL != ua); 533 for (unsigned int i = 0; i < ppoh->num_lock_uuids; i++) 534 { 535 GNUNET_assert (0 == 536 json_array_append_new (ua, 537 json_string ( 538 ppoh->lock_uuids[i]))); 539 } 540 GNUNET_assert (0 == 541 json_object_set_new (req, 542 "lock_uuids", 543 ua)); 544 } 545 if (! ppoh->create_token) 546 { 547 GNUNET_assert (0 == 548 json_object_set_new (req, 549 "create_token", 550 json_boolean (ppoh->create_token))); 551 } 552 eh = TALER_MERCHANT_curl_easy_get_ (ppoh->url); 553 if ( (NULL == eh) || 554 (GNUNET_OK != 555 TALER_curl_easy_post (&ppoh->post_ctx, 556 eh, 557 req)) ) 558 { 559 GNUNET_break (0); 560 json_decref (req); 561 if (NULL != eh) 562 curl_easy_cleanup (eh); 563 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 564 } 565 json_decref (req); 566 ppoh->job = GNUNET_CURL_job_add2 (ppoh->ctx, 567 eh, 568 ppoh->post_ctx.headers, 569 &handle_post_orders_finished, 570 ppoh); 571 if (NULL == ppoh->job) 572 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 573 return TALER_EC_NONE; 574 } 575 576 577 void 578 TALER_MERCHANT_post_private_orders_cancel ( 579 struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh) 580 { 581 if (NULL != ppoh->job) 582 { 583 GNUNET_CURL_job_cancel (ppoh->job); 584 ppoh->job = NULL; 585 } 586 TALER_curl_easy_post_finished (&ppoh->post_ctx); 587 json_decref (ppoh->order); 588 GNUNET_free (ppoh->url); 589 GNUNET_free (ppoh->base_url); 590 GNUNET_free (ppoh); 591 } 592 593 594 /* end of merchant_api_post-private-orders-new.c */