merchant_api_get-private-orders.c (16140B)
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_get-private-orders.c 19 * @brief Implementation of the GET /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/get-private-orders.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum number of orders we return. 35 */ 36 #define MAX_ORDERS 1024 37 38 39 /** 40 * Handle for a GET /private/orders operation. 41 */ 42 struct TALER_MERCHANT_GetPrivateOrdersHandle 43 { 44 /** 45 * Base URL of the merchant backend. 46 */ 47 char *base_url; 48 49 /** 50 * The full URL for this request. 51 */ 52 char *url; 53 54 /** 55 * Handle for the request. 56 */ 57 struct GNUNET_CURL_Job *job; 58 59 /** 60 * Function to call with the result. 61 */ 62 TALER_MERCHANT_GetPrivateOrdersCallback cb; 63 64 /** 65 * Closure for @a cb. 66 */ 67 TALER_MERCHANT_GET_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls; 68 69 /** 70 * Reference to the execution context. 71 */ 72 struct GNUNET_CURL_Context *ctx; 73 74 /** 75 * Paid filter. 76 */ 77 enum TALER_EXCHANGE_YesNoAll paid; 78 79 /** 80 * Refunded filter. 81 */ 82 enum TALER_EXCHANGE_YesNoAll refunded; 83 84 /** 85 * Wired filter. 86 */ 87 enum TALER_EXCHANGE_YesNoAll wired; 88 89 /** 90 * Expiration filter. 91 */ 92 enum TALER_EXCHANGE_YesNoAll expired; 93 94 /** 95 * Date threshold for filtering. 96 */ 97 struct GNUNET_TIME_Timestamp date; 98 99 /** 100 * Starting row for pagination. 101 */ 102 uint64_t offset; 103 104 /** 105 * Limit on number of results. 106 */ 107 int64_t limit; 108 109 /** 110 * Long polling timeout. 111 */ 112 struct GNUNET_TIME_Relative timeout; 113 114 /** 115 * Session ID filter, or NULL. 116 */ 117 char *session_id; 118 119 /** 120 * Fulfillment URL filter, or NULL. 121 */ 122 char *fulfillment_url; 123 124 /** 125 * Summary text filter, or NULL. 126 */ 127 char *summary_filter; 128 129 /** 130 * Maximum age filter. 131 * @since protocol v27. 132 */ 133 struct GNUNET_TIME_Relative max_age; 134 135 /** 136 * True if offset was explicitly set. 137 */ 138 bool have_offset; 139 140 /** 141 * True if date was explicitly set. 142 */ 143 bool have_date; 144 145 /** 146 * True if @e max_age was set. 147 */ 148 bool have_max_age; 149 }; 150 151 152 /** 153 * Parse order information from @a ia. 154 * 155 * @param ia JSON array (or NULL!) with order data 156 * @param[in,out] ogr response to fill 157 * @param oph operation handle 158 * @return #GNUNET_OK on success 159 */ 160 static enum GNUNET_GenericReturnValue 161 parse_orders (const json_t *ia, 162 struct TALER_MERCHANT_GetPrivateOrdersResponse *ogr, 163 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph) 164 { 165 unsigned int oes_len = (unsigned int) json_array_size (ia); 166 167 if ( (json_array_size (ia) != (size_t) oes_len) || 168 (oes_len > MAX_ORDERS) ) 169 { 170 GNUNET_break (0); 171 return GNUNET_SYSERR; 172 } 173 { 174 struct TALER_MERCHANT_GetPrivateOrdersOrderEntry oes[ 175 GNUNET_NZL (oes_len)]; 176 size_t index; 177 json_t *value; 178 179 memset (oes, 180 0, 181 sizeof (oes)); 182 json_array_foreach (ia, index, value) { 183 struct TALER_MERCHANT_GetPrivateOrdersOrderEntry *ie = &oes[index]; 184 struct GNUNET_JSON_Specification spec[] = { 185 TALER_JSON_spec_slug ("order_id", 186 &ie->order_id), 187 GNUNET_JSON_spec_timestamp ("timestamp", 188 &ie->timestamp), 189 GNUNET_JSON_spec_mark_optional ( 190 GNUNET_JSON_spec_timestamp ("pay_deadline", 191 &ie->pay_deadline), 192 NULL), 193 GNUNET_JSON_spec_uint64 ("row_id", 194 &ie->order_serial), 195 TALER_JSON_spec_amount_any ("amount", 196 &ie->amount), 197 GNUNET_JSON_spec_mark_optional ( 198 TALER_JSON_spec_amount_any ("refund_amount", 199 &ie->refund_amount), 200 NULL), 201 GNUNET_JSON_spec_mark_optional ( 202 TALER_JSON_spec_amount_any ("pending_refund_amount", 203 &ie->pending_refund_amount), 204 NULL), 205 GNUNET_JSON_spec_string ("summary", 206 &ie->summary), 207 GNUNET_JSON_spec_bool ("refundable", 208 &ie->refundable), 209 GNUNET_JSON_spec_bool ("paid", 210 &ie->paid), 211 GNUNET_JSON_spec_mark_optional ( 212 GNUNET_JSON_spec_bool ("wired", 213 &ie->wired), 214 NULL), 215 GNUNET_JSON_spec_end () 216 }; 217 218 if (GNUNET_OK != 219 GNUNET_JSON_parse (value, 220 spec, 221 NULL, NULL)) 222 { 223 GNUNET_break_op (0); 224 return GNUNET_SYSERR; 225 } 226 } 227 ogr->details.ok.orders_length = oes_len; 228 ogr->details.ok.orders = oes; 229 oph->cb (oph->cb_cls, 230 ogr); 231 oph->cb = NULL; 232 } 233 return GNUNET_OK; 234 } 235 236 237 /** 238 * Function called when we're done processing the 239 * HTTP GET /private/orders request. 240 * 241 * @param cls the `struct TALER_MERCHANT_GetPrivateOrdersHandle` 242 * @param response_code HTTP response code, 0 on error 243 * @param response response body, NULL if not in JSON 244 */ 245 static void 246 handle_get_orders_finished (void *cls, 247 long response_code, 248 const void *response) 249 { 250 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph = cls; 251 const json_t *json = response; 252 struct TALER_MERCHANT_GetPrivateOrdersResponse ogr = { 253 .hr.http_status = (unsigned int) response_code, 254 .hr.reply = json 255 }; 256 257 oph->job = NULL; 258 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 259 "Got /private/orders response with status code %u\n", 260 (unsigned int) response_code); 261 switch (response_code) 262 { 263 case MHD_HTTP_OK: 264 { 265 const json_t *orders; 266 struct GNUNET_JSON_Specification spec[] = { 267 GNUNET_JSON_spec_array_const ("orders", 268 &orders), 269 GNUNET_JSON_spec_end () 270 }; 271 272 if (GNUNET_OK != 273 GNUNET_JSON_parse (json, 274 spec, 275 NULL, NULL)) 276 { 277 ogr.hr.http_status = 0; 278 ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 279 break; 280 } 281 if (GNUNET_OK == 282 parse_orders (orders, 283 &ogr, 284 oph)) 285 { 286 TALER_MERCHANT_get_private_orders_cancel (oph); 287 return; 288 } 289 ogr.hr.http_status = 0; 290 ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 291 break; 292 } 293 case MHD_HTTP_UNAUTHORIZED: 294 ogr.hr.ec = TALER_JSON_get_error_code (json); 295 ogr.hr.hint = TALER_JSON_get_error_hint (json); 296 break; 297 case MHD_HTTP_NOT_FOUND: 298 ogr.hr.ec = TALER_JSON_get_error_code (json); 299 ogr.hr.hint = TALER_JSON_get_error_hint (json); 300 break; 301 default: 302 ogr.hr.ec = TALER_JSON_get_error_code (json); 303 ogr.hr.hint = TALER_JSON_get_error_hint (json); 304 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 305 "Unexpected response code %u/%d\n", 306 (unsigned int) response_code, 307 (int) ogr.hr.ec); 308 break; 309 } 310 oph->cb (oph->cb_cls, 311 &ogr); 312 TALER_MERCHANT_get_private_orders_cancel (oph); 313 } 314 315 316 struct TALER_MERCHANT_GetPrivateOrdersHandle * 317 TALER_MERCHANT_get_private_orders_create ( 318 struct GNUNET_CURL_Context *ctx, 319 const char *url) 320 { 321 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph; 322 323 oph = GNUNET_new (struct TALER_MERCHANT_GetPrivateOrdersHandle); 324 oph->ctx = ctx; 325 oph->base_url = GNUNET_strdup (url); 326 oph->paid = TALER_EXCHANGE_YNA_ALL; 327 oph->refunded = TALER_EXCHANGE_YNA_ALL; 328 oph->wired = TALER_EXCHANGE_YNA_ALL; 329 oph->expired = TALER_EXCHANGE_YNA_ALL; 330 oph->date = GNUNET_TIME_UNIT_FOREVER_TS; 331 oph->offset = UINT64_MAX; 332 oph->limit = -20; 333 return oph; 334 } 335 336 337 enum GNUNET_GenericReturnValue 338 TALER_MERCHANT_get_private_orders_set_options_ ( 339 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph, 340 unsigned int num_options, 341 const struct TALER_MERCHANT_GetPrivateOrdersOptionValue *options) 342 { 343 for (unsigned int i = 0; i < num_options; i++) 344 { 345 const struct TALER_MERCHANT_GetPrivateOrdersOptionValue *opt = 346 &options[i]; 347 348 switch (opt->option) 349 { 350 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_END: 351 return GNUNET_OK; 352 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_PAID: 353 oph->paid = opt->details.paid; 354 break; 355 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_REFUNDED: 356 oph->refunded = opt->details.refunded; 357 break; 358 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_WIRED: 359 oph->wired = opt->details.wired; 360 break; 361 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_EXPIRED: 362 oph->expired = opt->details.expired; 363 break; 364 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_LIMIT: 365 oph->limit = opt->details.limit; 366 break; 367 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_OFFSET: 368 oph->offset = opt->details.offset; 369 oph->have_offset = true; 370 break; 371 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_DATE: 372 oph->date = opt->details.date; 373 oph->have_date = true; 374 break; 375 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_TIMEOUT: 376 oph->timeout = opt->details.timeout; 377 break; 378 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SESSION_ID: 379 GNUNET_free (oph->session_id); 380 if (NULL != opt->details.session_id) 381 oph->session_id = GNUNET_strdup (opt->details.session_id); 382 break; 383 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_FULFILLMENT_URL: 384 GNUNET_free (oph->fulfillment_url); 385 if (NULL != opt->details.fulfillment_url) 386 oph->fulfillment_url = GNUNET_strdup (opt->details.fulfillment_url); 387 break; 388 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SUMMARY_FILTER: 389 GNUNET_free (oph->summary_filter); 390 if (NULL != opt->details.summary_filter) 391 oph->summary_filter = GNUNET_strdup (opt->details.summary_filter); 392 break; 393 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_MAX_AGE: 394 oph->max_age = opt->details.max_age; 395 oph->have_max_age = true; 396 break; 397 default: 398 GNUNET_break (0); 399 return GNUNET_NO; 400 } 401 } 402 return GNUNET_OK; 403 } 404 405 406 enum TALER_ErrorCode 407 TALER_MERCHANT_get_private_orders_start ( 408 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph, 409 TALER_MERCHANT_GetPrivateOrdersCallback cb, 410 TALER_MERCHANT_GET_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls) 411 { 412 CURL *eh; 413 unsigned int tms; 414 415 oph->cb = cb; 416 oph->cb_cls = cb_cls; 417 tms = (unsigned int) (oph->timeout.rel_value_us 418 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 419 { 420 char dstr[30]; 421 bool have_date; 422 bool have_srow; 423 char cbuf[30]; 424 char dbuf[30]; 425 char tbuf[30]; 426 char mabuf[30]; 427 428 GNUNET_snprintf (tbuf, 429 sizeof (tbuf), 430 "%u", 431 tms); 432 if (oph->have_max_age) 433 GNUNET_snprintf (mabuf, 434 sizeof (mabuf), 435 "%llu", 436 (unsigned long long) 437 (oph->max_age.rel_value_us 438 / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); 439 GNUNET_snprintf (dbuf, 440 sizeof (dbuf), 441 "%lld", 442 (long long) oph->limit); 443 GNUNET_snprintf (cbuf, 444 sizeof (cbuf), 445 "%llu", 446 (unsigned long long) oph->offset); 447 GNUNET_snprintf (dstr, 448 sizeof (dstr), 449 "%llu", 450 (unsigned long long) GNUNET_TIME_timestamp_to_s ( 451 oph->date)); 452 if (oph->limit > 0) 453 { 454 have_date = oph->have_date 455 && ! GNUNET_TIME_absolute_is_zero (oph->date.abs_time); 456 have_srow = oph->have_offset 457 && (0 != oph->offset); 458 } 459 else 460 { 461 have_date = oph->have_date 462 && ! GNUNET_TIME_absolute_is_never (oph->date.abs_time); 463 have_srow = oph->have_offset 464 && (UINT64_MAX != oph->offset); 465 } 466 oph->url = TALER_url_join (oph->base_url, 467 "private/orders", 468 "paid", 469 (TALER_EXCHANGE_YNA_ALL != oph->paid) 470 ? TALER_yna_to_string (oph->paid) 471 : NULL, 472 "refunded", 473 (TALER_EXCHANGE_YNA_ALL != oph->refunded) 474 ? TALER_yna_to_string (oph->refunded) 475 : NULL, 476 "wired", 477 (TALER_EXCHANGE_YNA_ALL != oph->wired) 478 ? TALER_yna_to_string (oph->wired) 479 : NULL, 480 "expired", 481 (TALER_EXCHANGE_YNA_ALL != oph->expired) 482 ? TALER_yna_to_string (oph->expired) 483 : NULL, 484 "date_s", 485 have_date 486 ? dstr 487 : NULL, 488 "offset", 489 have_srow 490 ? cbuf 491 : NULL, 492 "limit", 493 (-20 != oph->limit) 494 ? dbuf 495 : NULL, 496 "timeout_ms", 497 (0 != tms) 498 ? tbuf 499 : NULL, 500 "session_id", 501 oph->session_id, 502 "fulfillment_url", 503 oph->fulfillment_url, 504 "summary_filter", 505 oph->summary_filter, 506 "max_age_s", 507 oph->have_max_age 508 ? mabuf 509 : NULL, 510 NULL); 511 } 512 if (NULL == oph->url) 513 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 514 eh = TALER_MERCHANT_curl_easy_get_ (oph->url); 515 if (NULL == eh) 516 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 517 if (0 != tms) 518 { 519 GNUNET_break (CURLE_OK == 520 curl_easy_setopt (eh, 521 CURLOPT_TIMEOUT_MS, 522 (long) (tms + 100L))); 523 } 524 oph->job = GNUNET_CURL_job_add (oph->ctx, 525 eh, 526 &handle_get_orders_finished, 527 oph); 528 if (NULL == oph->job) 529 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 530 return TALER_EC_NONE; 531 } 532 533 534 void 535 TALER_MERCHANT_get_private_orders_cancel ( 536 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph) 537 { 538 if (NULL != oph->job) 539 { 540 GNUNET_CURL_job_cancel (oph->job); 541 oph->job = NULL; 542 } 543 GNUNET_free (oph->url); 544 GNUNET_free (oph->session_id); 545 GNUNET_free (oph->fulfillment_url); 546 GNUNET_free (oph->summary_filter); 547 GNUNET_free (oph->base_url); 548 GNUNET_free (oph); 549 } 550 551 552 /* end of merchant_api_get-private-orders-new.c */