merchant_api_get-private-orders.c (15994B)
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_end () 212 }; 213 214 if (GNUNET_OK != 215 GNUNET_JSON_parse (value, 216 spec, 217 NULL, NULL)) 218 { 219 GNUNET_break_op (0); 220 return GNUNET_SYSERR; 221 } 222 } 223 ogr->details.ok.orders_length = oes_len; 224 ogr->details.ok.orders = oes; 225 oph->cb (oph->cb_cls, 226 ogr); 227 oph->cb = NULL; 228 } 229 return GNUNET_OK; 230 } 231 232 233 /** 234 * Function called when we're done processing the 235 * HTTP GET /private/orders request. 236 * 237 * @param cls the `struct TALER_MERCHANT_GetPrivateOrdersHandle` 238 * @param response_code HTTP response code, 0 on error 239 * @param response response body, NULL if not in JSON 240 */ 241 static void 242 handle_get_orders_finished (void *cls, 243 long response_code, 244 const void *response) 245 { 246 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph = cls; 247 const json_t *json = response; 248 struct TALER_MERCHANT_GetPrivateOrdersResponse ogr = { 249 .hr.http_status = (unsigned int) response_code, 250 .hr.reply = json 251 }; 252 253 oph->job = NULL; 254 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 255 "Got /private/orders response with status code %u\n", 256 (unsigned int) response_code); 257 switch (response_code) 258 { 259 case MHD_HTTP_OK: 260 { 261 const json_t *orders; 262 struct GNUNET_JSON_Specification spec[] = { 263 GNUNET_JSON_spec_array_const ("orders", 264 &orders), 265 GNUNET_JSON_spec_end () 266 }; 267 268 if (GNUNET_OK != 269 GNUNET_JSON_parse (json, 270 spec, 271 NULL, NULL)) 272 { 273 ogr.hr.http_status = 0; 274 ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 275 break; 276 } 277 if (GNUNET_OK == 278 parse_orders (orders, 279 &ogr, 280 oph)) 281 { 282 TALER_MERCHANT_get_private_orders_cancel (oph); 283 return; 284 } 285 ogr.hr.http_status = 0; 286 ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 287 break; 288 } 289 case MHD_HTTP_UNAUTHORIZED: 290 ogr.hr.ec = TALER_JSON_get_error_code (json); 291 ogr.hr.hint = TALER_JSON_get_error_hint (json); 292 break; 293 case MHD_HTTP_NOT_FOUND: 294 ogr.hr.ec = TALER_JSON_get_error_code (json); 295 ogr.hr.hint = TALER_JSON_get_error_hint (json); 296 break; 297 default: 298 ogr.hr.ec = TALER_JSON_get_error_code (json); 299 ogr.hr.hint = TALER_JSON_get_error_hint (json); 300 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 301 "Unexpected response code %u/%d\n", 302 (unsigned int) response_code, 303 (int) ogr.hr.ec); 304 break; 305 } 306 oph->cb (oph->cb_cls, 307 &ogr); 308 TALER_MERCHANT_get_private_orders_cancel (oph); 309 } 310 311 312 struct TALER_MERCHANT_GetPrivateOrdersHandle * 313 TALER_MERCHANT_get_private_orders_create ( 314 struct GNUNET_CURL_Context *ctx, 315 const char *url) 316 { 317 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph; 318 319 oph = GNUNET_new (struct TALER_MERCHANT_GetPrivateOrdersHandle); 320 oph->ctx = ctx; 321 oph->base_url = GNUNET_strdup (url); 322 oph->paid = TALER_EXCHANGE_YNA_ALL; 323 oph->refunded = TALER_EXCHANGE_YNA_ALL; 324 oph->wired = TALER_EXCHANGE_YNA_ALL; 325 oph->expired = TALER_EXCHANGE_YNA_ALL; 326 oph->date = GNUNET_TIME_UNIT_FOREVER_TS; 327 oph->offset = UINT64_MAX; 328 oph->limit = -20; 329 return oph; 330 } 331 332 333 enum GNUNET_GenericReturnValue 334 TALER_MERCHANT_get_private_orders_set_options_ ( 335 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph, 336 unsigned int num_options, 337 const struct TALER_MERCHANT_GetPrivateOrdersOptionValue *options) 338 { 339 for (unsigned int i = 0; i < num_options; i++) 340 { 341 const struct TALER_MERCHANT_GetPrivateOrdersOptionValue *opt = 342 &options[i]; 343 344 switch (opt->option) 345 { 346 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_END: 347 return GNUNET_OK; 348 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_PAID: 349 oph->paid = opt->details.paid; 350 break; 351 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_REFUNDED: 352 oph->refunded = opt->details.refunded; 353 break; 354 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_WIRED: 355 oph->wired = opt->details.wired; 356 break; 357 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_EXPIRED: 358 oph->expired = opt->details.expired; 359 break; 360 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_LIMIT: 361 oph->limit = opt->details.limit; 362 break; 363 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_OFFSET: 364 oph->offset = opt->details.offset; 365 oph->have_offset = true; 366 break; 367 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_DATE: 368 oph->date = opt->details.date; 369 oph->have_date = true; 370 break; 371 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_TIMEOUT: 372 oph->timeout = opt->details.timeout; 373 break; 374 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SESSION_ID: 375 GNUNET_free (oph->session_id); 376 if (NULL != opt->details.session_id) 377 oph->session_id = GNUNET_strdup (opt->details.session_id); 378 break; 379 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_FULFILLMENT_URL: 380 GNUNET_free (oph->fulfillment_url); 381 if (NULL != opt->details.fulfillment_url) 382 oph->fulfillment_url = GNUNET_strdup (opt->details.fulfillment_url); 383 break; 384 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SUMMARY_FILTER: 385 GNUNET_free (oph->summary_filter); 386 if (NULL != opt->details.summary_filter) 387 oph->summary_filter = GNUNET_strdup (opt->details.summary_filter); 388 break; 389 case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_MAX_AGE: 390 oph->max_age = opt->details.max_age; 391 oph->have_max_age = true; 392 break; 393 default: 394 GNUNET_break (0); 395 return GNUNET_NO; 396 } 397 } 398 return GNUNET_OK; 399 } 400 401 402 enum TALER_ErrorCode 403 TALER_MERCHANT_get_private_orders_start ( 404 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph, 405 TALER_MERCHANT_GetPrivateOrdersCallback cb, 406 TALER_MERCHANT_GET_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls) 407 { 408 CURL *eh; 409 unsigned int tms; 410 411 oph->cb = cb; 412 oph->cb_cls = cb_cls; 413 tms = (unsigned int) (oph->timeout.rel_value_us 414 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 415 { 416 char dstr[30]; 417 bool have_date; 418 bool have_srow; 419 char cbuf[30]; 420 char dbuf[30]; 421 char tbuf[30]; 422 char mabuf[30]; 423 424 GNUNET_snprintf (tbuf, 425 sizeof (tbuf), 426 "%u", 427 tms); 428 if (oph->have_max_age) 429 GNUNET_snprintf (mabuf, 430 sizeof (mabuf), 431 "%llu", 432 (unsigned long long) 433 (oph->max_age.rel_value_us 434 / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); 435 GNUNET_snprintf (dbuf, 436 sizeof (dbuf), 437 "%lld", 438 (long long) oph->limit); 439 GNUNET_snprintf (cbuf, 440 sizeof (cbuf), 441 "%llu", 442 (unsigned long long) oph->offset); 443 GNUNET_snprintf (dstr, 444 sizeof (dstr), 445 "%llu", 446 (unsigned long long) GNUNET_TIME_timestamp_to_s ( 447 oph->date)); 448 if (oph->limit > 0) 449 { 450 have_date = oph->have_date 451 && ! GNUNET_TIME_absolute_is_zero (oph->date.abs_time); 452 have_srow = oph->have_offset 453 && (0 != oph->offset); 454 } 455 else 456 { 457 have_date = oph->have_date 458 && ! GNUNET_TIME_absolute_is_never (oph->date.abs_time); 459 have_srow = oph->have_offset 460 && (UINT64_MAX != oph->offset); 461 } 462 oph->url = TALER_url_join (oph->base_url, 463 "private/orders", 464 "paid", 465 (TALER_EXCHANGE_YNA_ALL != oph->paid) 466 ? TALER_yna_to_string (oph->paid) 467 : NULL, 468 "refunded", 469 (TALER_EXCHANGE_YNA_ALL != oph->refunded) 470 ? TALER_yna_to_string (oph->refunded) 471 : NULL, 472 "wired", 473 (TALER_EXCHANGE_YNA_ALL != oph->wired) 474 ? TALER_yna_to_string (oph->wired) 475 : NULL, 476 "expired", 477 (TALER_EXCHANGE_YNA_ALL != oph->expired) 478 ? TALER_yna_to_string (oph->expired) 479 : NULL, 480 "date_s", 481 have_date 482 ? dstr 483 : NULL, 484 "offset", 485 have_srow 486 ? cbuf 487 : NULL, 488 "limit", 489 (-20 != oph->limit) 490 ? dbuf 491 : NULL, 492 "timeout_ms", 493 (0 != tms) 494 ? tbuf 495 : NULL, 496 "session_id", 497 oph->session_id, 498 "fulfillment_url", 499 oph->fulfillment_url, 500 "summary_filter", 501 oph->summary_filter, 502 "max_age_s", 503 oph->have_max_age 504 ? mabuf 505 : NULL, 506 NULL); 507 } 508 if (NULL == oph->url) 509 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 510 eh = TALER_MERCHANT_curl_easy_get_ (oph->url); 511 if (NULL == eh) 512 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 513 if (0 != tms) 514 { 515 GNUNET_break (CURLE_OK == 516 curl_easy_setopt (eh, 517 CURLOPT_TIMEOUT_MS, 518 (long) (tms + 100L))); 519 } 520 oph->job = GNUNET_CURL_job_add (oph->ctx, 521 eh, 522 &handle_get_orders_finished, 523 oph); 524 if (NULL == oph->job) 525 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 526 return TALER_EC_NONE; 527 } 528 529 530 void 531 TALER_MERCHANT_get_private_orders_cancel ( 532 struct TALER_MERCHANT_GetPrivateOrdersHandle *oph) 533 { 534 if (NULL != oph->job) 535 { 536 GNUNET_CURL_job_cancel (oph->job); 537 oph->job = NULL; 538 } 539 GNUNET_free (oph->url); 540 GNUNET_free (oph->session_id); 541 GNUNET_free (oph->fulfillment_url); 542 GNUNET_free (oph->summary_filter); 543 GNUNET_free (oph->base_url); 544 GNUNET_free (oph); 545 } 546 547 548 /* end of merchant_api_get-private-orders-new.c */