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