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