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