merchant_api_get_orders.c (13015B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 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_orders.c 19 * @brief Implementation of the GET /private/orders request of the merchant's HTTP API 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_service.h" 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 #include <taler/taler_signatures.h> 32 33 /** 34 * Maximum number of orders we return. 35 */ 36 #define MAX_ORDERS 1024 37 38 /** 39 * Handle for a GET /orders operation. 40 */ 41 struct TALER_MERCHANT_OrdersGetHandle 42 { 43 /** 44 * The url for this request. 45 */ 46 char *url; 47 48 /** 49 * Handle for the request. 50 */ 51 struct GNUNET_CURL_Job *job; 52 53 /** 54 * Function to call with the result. 55 */ 56 TALER_MERCHANT_OrdersGetCallback cb; 57 58 /** 59 * Closure for @a cb. 60 */ 61 void *cb_cls; 62 63 /** 64 * Reference to the execution context. 65 */ 66 struct GNUNET_CURL_Context *ctx; 67 68 }; 69 70 71 /** 72 * Parse order information from @a ia. 73 * 74 * @param ia JSON array (or NULL!) with order data 75 * @param[in] ogr response to fill 76 * @param ogh operation handle 77 * @return #GNUNET_OK on success 78 */ 79 static enum GNUNET_GenericReturnValue 80 parse_orders (const json_t *ia, 81 struct TALER_MERCHANT_OrdersGetResponse *ogr, 82 struct TALER_MERCHANT_OrdersGetHandle *ogh) 83 { 84 unsigned int oes_len = (unsigned int) json_array_size (ia); 85 86 if ( (json_array_size (ia) != (size_t) oes_len) || 87 (oes_len > MAX_ORDERS) ) 88 { 89 GNUNET_break (0); 90 return GNUNET_SYSERR; 91 } 92 { 93 struct TALER_MERCHANT_OrderEntry oes[GNUNET_NZL (oes_len)]; 94 size_t index; 95 json_t *value; 96 97 memset (oes, 98 0, 99 sizeof (oes)); 100 json_array_foreach (ia, index, value) { 101 struct TALER_MERCHANT_OrderEntry *ie = &oes[index]; 102 struct GNUNET_JSON_Specification spec[] = { 103 GNUNET_JSON_spec_string ("order_id", 104 &ie->order_id), 105 GNUNET_JSON_spec_timestamp ("timestamp", 106 &ie->timestamp), 107 GNUNET_JSON_spec_uint64 ("row_id", 108 &ie->order_serial), 109 TALER_JSON_spec_amount_any ("amount", 110 &ie->amount), 111 GNUNET_JSON_spec_mark_optional ( 112 TALER_JSON_spec_amount_any ("refund_amount", 113 &ie->refund_amount), 114 NULL), 115 GNUNET_JSON_spec_mark_optional ( 116 TALER_JSON_spec_amount_any ("pending_refund_amount", 117 &ie->pending_refund_amount), 118 NULL), 119 GNUNET_JSON_spec_string ("summary", 120 &ie->summary), 121 GNUNET_JSON_spec_bool ("refundable", 122 &ie->refundable), 123 GNUNET_JSON_spec_bool ("paid", 124 &ie->paid), 125 GNUNET_JSON_spec_end () 126 }; 127 128 if (GNUNET_OK != 129 GNUNET_JSON_parse (value, 130 spec, 131 NULL, NULL)) 132 { 133 GNUNET_break_op (0); 134 return GNUNET_SYSERR; 135 } 136 } 137 ogr->details.ok.orders_length = oes_len; 138 ogr->details.ok.orders = oes; 139 ogh->cb (ogh->cb_cls, 140 ogr); 141 ogh->cb = NULL; /* just to be sure */ 142 } 143 return GNUNET_OK; 144 } 145 146 147 /** 148 * Function called when we're done processing the 149 * HTTP /orders request. 150 * 151 * @param cls the `struct TALER_MERCHANT_OrdersGetHandle` 152 * @param response_code HTTP response code, 0 on error 153 * @param response response body, NULL if not in JSON 154 */ 155 static void 156 handle_get_orders_finished (void *cls, 157 long response_code, 158 const void *response) 159 { 160 struct TALER_MERCHANT_OrdersGetHandle *ogh = cls; 161 const json_t *json = response; 162 struct TALER_MERCHANT_OrdersGetResponse ogr = { 163 .hr.http_status = (unsigned int) response_code, 164 .hr.reply = json 165 }; 166 167 ogh->job = NULL; 168 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 169 "Got /orders response with status code %u\n", 170 (unsigned int) response_code); 171 switch (response_code) 172 { 173 case MHD_HTTP_OK: 174 { 175 const json_t *orders; 176 struct GNUNET_JSON_Specification spec[] = { 177 GNUNET_JSON_spec_array_const ("orders", 178 &orders), 179 GNUNET_JSON_spec_end () 180 }; 181 182 if (GNUNET_OK != 183 GNUNET_JSON_parse (json, 184 spec, 185 NULL, NULL)) 186 { 187 ogr.hr.http_status = 0; 188 ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 189 break; 190 } 191 if (GNUNET_OK == 192 parse_orders (orders, 193 &ogr, 194 ogh)) 195 { 196 TALER_MERCHANT_orders_get_cancel (ogh); 197 return; 198 } 199 ogr.hr.http_status = 0; 200 ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 201 break; 202 } 203 case MHD_HTTP_UNAUTHORIZED: 204 ogr.hr.ec = TALER_JSON_get_error_code (json); 205 ogr.hr.hint = TALER_JSON_get_error_hint (json); 206 /* Nothing really to verify, merchant says we need to authenticate. */ 207 break; 208 case MHD_HTTP_NOT_FOUND: 209 ogr.hr.ec = TALER_JSON_get_error_code (json); 210 ogr.hr.hint = TALER_JSON_get_error_hint (json); 211 break; 212 default: 213 /* unexpected response code */ 214 ogr.hr.ec = TALER_JSON_get_error_code (json); 215 ogr.hr.hint = TALER_JSON_get_error_hint (json); 216 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 217 "Unexpected response code %u/%d\n", 218 (unsigned int) response_code, 219 (int) ogr.hr.ec); 220 break; 221 } 222 ogh->cb (ogh->cb_cls, 223 &ogr); 224 TALER_MERCHANT_orders_get_cancel (ogh); 225 } 226 227 228 struct TALER_MERCHANT_OrdersGetHandle * 229 TALER_MERCHANT_orders_get ( 230 struct GNUNET_CURL_Context *ctx, 231 const char *backend_url, 232 TALER_MERCHANT_OrdersGetCallback cb, 233 void *cb_cls) 234 { 235 return TALER_MERCHANT_orders_get2 (ctx, 236 backend_url, 237 TALER_EXCHANGE_YNA_ALL, 238 TALER_EXCHANGE_YNA_ALL, 239 TALER_EXCHANGE_YNA_ALL, 240 GNUNET_TIME_UNIT_FOREVER_TS, 241 UINT64_MAX, 242 -20, /* default is most recent 20 entries */ 243 GNUNET_TIME_UNIT_ZERO, 244 cb, 245 cb_cls); 246 } 247 248 249 struct TALER_MERCHANT_OrdersGetHandle * 250 TALER_MERCHANT_orders_get2 ( 251 struct GNUNET_CURL_Context *ctx, 252 const char *backend_url, 253 enum TALER_EXCHANGE_YesNoAll paid, 254 enum TALER_EXCHANGE_YesNoAll refunded, 255 enum TALER_EXCHANGE_YesNoAll wired, 256 struct GNUNET_TIME_Timestamp date, 257 uint64_t start_row, 258 int64_t delta, 259 struct GNUNET_TIME_Relative timeout, 260 TALER_MERCHANT_OrdersGetCallback cb, 261 void *cb_cls) 262 { 263 return TALER_MERCHANT_orders_get3 ( 264 ctx, 265 backend_url, 266 paid, 267 refunded, 268 wired, 269 NULL, 270 NULL, 271 date, 272 start_row, 273 delta, 274 timeout, 275 cb, 276 cb_cls); 277 } 278 279 280 struct TALER_MERCHANT_OrdersGetHandle * 281 TALER_MERCHANT_orders_get3 ( 282 struct GNUNET_CURL_Context *ctx, 283 const char *backend_url, 284 enum TALER_EXCHANGE_YesNoAll paid, 285 enum TALER_EXCHANGE_YesNoAll refunded, 286 enum TALER_EXCHANGE_YesNoAll wired, 287 const char *session_id, 288 const char *fulfillment_url, 289 struct GNUNET_TIME_Timestamp date, 290 uint64_t start_row, 291 int64_t delta, 292 struct GNUNET_TIME_Relative timeout, 293 TALER_MERCHANT_OrdersGetCallback cb, 294 void *cb_cls) 295 { 296 struct TALER_MERCHANT_OrdersGetHandle *ogh; 297 CURL *eh; 298 unsigned int tms = timeout.rel_value_us 299 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 300 301 GNUNET_assert (NULL != backend_url); 302 if ( (delta > MAX_ORDERS) || 303 (delta < -MAX_ORDERS) ) 304 { 305 GNUNET_break (0); 306 return NULL; 307 } 308 if (0 == delta) 309 { 310 GNUNET_break (0); 311 return NULL; 312 } 313 ogh = GNUNET_new (struct TALER_MERCHANT_OrdersGetHandle); 314 ogh->ctx = ctx; 315 ogh->cb = cb; 316 ogh->cb_cls = cb_cls; 317 318 /* build ogh->url with the various optional arguments */ 319 { 320 char dstr[30]; 321 char *fec = NULL; 322 char *sid = NULL; 323 bool have_date; 324 bool have_srow; 325 char cbuf[30]; 326 char dbuf[30]; 327 char tbuf[30]; 328 329 GNUNET_snprintf (tbuf, 330 sizeof (tbuf), 331 "%u", 332 tms); 333 GNUNET_snprintf (dbuf, 334 sizeof (dbuf), 335 "%lld", 336 (long long) delta); 337 GNUNET_snprintf (cbuf, 338 sizeof (cbuf), 339 "%llu", 340 (unsigned long long) start_row); 341 if (NULL != session_id) 342 (void) GNUNET_STRINGS_urlencode (strlen (session_id), 343 session_id, 344 &sid); 345 if (NULL != fulfillment_url) 346 (void) GNUNET_STRINGS_urlencode (strlen (fulfillment_url), 347 fulfillment_url, 348 &fec); 349 GNUNET_snprintf (dstr, 350 sizeof (dstr), 351 "%llu", 352 (unsigned long long) GNUNET_TIME_timestamp_to_s (date)); 353 if (delta > 0) 354 { 355 have_date = ! GNUNET_TIME_absolute_is_zero (date.abs_time); 356 have_srow = (0 != start_row); 357 } 358 else 359 { 360 have_date = ! GNUNET_TIME_absolute_is_never (date.abs_time); 361 have_srow = (UINT64_MAX != start_row); 362 } 363 ogh->url = TALER_url_join (backend_url, 364 "private/orders", 365 "paid", 366 (TALER_EXCHANGE_YNA_ALL != paid) 367 ? TALER_yna_to_string (paid) 368 : NULL, 369 "refunded", 370 (TALER_EXCHANGE_YNA_ALL != refunded) 371 ? TALER_yna_to_string (refunded) 372 : NULL, 373 "wired", 374 (TALER_EXCHANGE_YNA_ALL != wired) 375 ? TALER_yna_to_string (wired) 376 : NULL, 377 "date_s", 378 (have_date) 379 ? dstr 380 : NULL, 381 "start", 382 (have_srow) 383 ? cbuf 384 : NULL, 385 "delta", 386 (-20 != delta) 387 ? dbuf 388 : NULL, 389 "timeout_ms", 390 (0 != tms) 391 ? tbuf 392 : NULL, 393 "session_id", 394 sid, 395 "fulfillment_url", 396 fec, 397 NULL); 398 GNUNET_free (sid); 399 GNUNET_free (fec); 400 } 401 if (NULL == ogh->url) 402 { 403 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 404 "Could not construct request URL.\n"); 405 GNUNET_free (ogh); 406 return NULL; 407 } 408 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 409 "Requesting URL '%s'\n", 410 ogh->url); 411 eh = TALER_MERCHANT_curl_easy_get_ (ogh->url); 412 if (NULL == eh) 413 { 414 GNUNET_break (0); 415 GNUNET_free (ogh->url); 416 GNUNET_free (ogh); 417 return NULL; 418 } 419 if (0 != tms) 420 { 421 GNUNET_break (CURLE_OK == 422 curl_easy_setopt (eh, 423 CURLOPT_TIMEOUT_MS, 424 (long) (tms + 100L))); 425 } 426 ogh->job = GNUNET_CURL_job_add (ctx, 427 eh, 428 &handle_get_orders_finished, 429 ogh); 430 return ogh; 431 } 432 433 434 void 435 TALER_MERCHANT_orders_get_cancel ( 436 struct TALER_MERCHANT_OrdersGetHandle *ogh) 437 { 438 if (NULL != ogh->job) 439 GNUNET_CURL_job_cancel (ogh->job); 440 GNUNET_free (ogh->url); 441 GNUNET_free (ogh); 442 }