Line data Source code
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/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 0 : parse_orders (const json_t *ia,
157 : struct TALER_MERCHANT_GetPrivateOrdersResponse *ogr,
158 : struct TALER_MERCHANT_GetPrivateOrdersHandle *oph)
159 : {
160 0 : unsigned int oes_len = (unsigned int) json_array_size (ia);
161 :
162 0 : if ( (json_array_size (ia) != (size_t) oes_len) ||
163 : (oes_len > MAX_ORDERS) )
164 : {
165 0 : GNUNET_break (0);
166 0 : return GNUNET_SYSERR;
167 : }
168 0 : {
169 0 : struct TALER_MERCHANT_GetPrivateOrdersOrderEntry oes[
170 0 : GNUNET_NZL (oes_len)];
171 : size_t index;
172 : json_t *value;
173 :
174 0 : memset (oes,
175 : 0,
176 : sizeof (oes));
177 0 : json_array_foreach (ia, index, value) {
178 0 : struct TALER_MERCHANT_GetPrivateOrdersOrderEntry *ie = &oes[index];
179 : struct GNUNET_JSON_Specification spec[] = {
180 0 : GNUNET_JSON_spec_string ("order_id",
181 : &ie->order_id),
182 0 : GNUNET_JSON_spec_timestamp ("timestamp",
183 : &ie->timestamp),
184 0 : GNUNET_JSON_spec_uint64 ("row_id",
185 : &ie->order_serial),
186 0 : TALER_JSON_spec_amount_any ("amount",
187 : &ie->amount),
188 0 : GNUNET_JSON_spec_mark_optional (
189 : TALER_JSON_spec_amount_any ("refund_amount",
190 : &ie->refund_amount),
191 : NULL),
192 0 : GNUNET_JSON_spec_mark_optional (
193 : TALER_JSON_spec_amount_any ("pending_refund_amount",
194 : &ie->pending_refund_amount),
195 : NULL),
196 0 : GNUNET_JSON_spec_string ("summary",
197 : &ie->summary),
198 0 : GNUNET_JSON_spec_bool ("refundable",
199 : &ie->refundable),
200 0 : GNUNET_JSON_spec_bool ("paid",
201 : &ie->paid),
202 0 : GNUNET_JSON_spec_end ()
203 : };
204 :
205 0 : if (GNUNET_OK !=
206 0 : GNUNET_JSON_parse (value,
207 : spec,
208 : NULL, NULL))
209 : {
210 0 : GNUNET_break_op (0);
211 0 : return GNUNET_SYSERR;
212 : }
213 : }
214 0 : ogr->details.ok.orders_length = oes_len;
215 0 : ogr->details.ok.orders = oes;
216 0 : oph->cb (oph->cb_cls,
217 : ogr);
218 0 : oph->cb = NULL;
219 : }
220 0 : 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 0 : handle_get_orders_finished (void *cls,
234 : long response_code,
235 : const void *response)
236 : {
237 0 : struct TALER_MERCHANT_GetPrivateOrdersHandle *oph = cls;
238 0 : const json_t *json = response;
239 0 : struct TALER_MERCHANT_GetPrivateOrdersResponse ogr = {
240 0 : .hr.http_status = (unsigned int) response_code,
241 : .hr.reply = json
242 : };
243 :
244 0 : oph->job = NULL;
245 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
246 : "Got /private/orders response with status code %u\n",
247 : (unsigned int) response_code);
248 0 : switch (response_code)
249 : {
250 0 : case MHD_HTTP_OK:
251 : {
252 : const json_t *orders;
253 : struct GNUNET_JSON_Specification spec[] = {
254 0 : GNUNET_JSON_spec_array_const ("orders",
255 : &orders),
256 0 : GNUNET_JSON_spec_end ()
257 : };
258 :
259 0 : if (GNUNET_OK !=
260 0 : GNUNET_JSON_parse (json,
261 : spec,
262 : NULL, NULL))
263 : {
264 0 : ogr.hr.http_status = 0;
265 0 : ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
266 0 : break;
267 : }
268 0 : if (GNUNET_OK ==
269 0 : parse_orders (orders,
270 : &ogr,
271 : oph))
272 : {
273 0 : TALER_MERCHANT_get_private_orders_cancel (oph);
274 0 : return;
275 : }
276 0 : ogr.hr.http_status = 0;
277 0 : ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
278 0 : break;
279 : }
280 0 : case MHD_HTTP_UNAUTHORIZED:
281 0 : ogr.hr.ec = TALER_JSON_get_error_code (json);
282 0 : ogr.hr.hint = TALER_JSON_get_error_hint (json);
283 0 : break;
284 0 : case MHD_HTTP_NOT_FOUND:
285 0 : ogr.hr.ec = TALER_JSON_get_error_code (json);
286 0 : ogr.hr.hint = TALER_JSON_get_error_hint (json);
287 0 : break;
288 0 : default:
289 0 : ogr.hr.ec = TALER_JSON_get_error_code (json);
290 0 : ogr.hr.hint = TALER_JSON_get_error_hint (json);
291 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
292 : "Unexpected response code %u/%d\n",
293 : (unsigned int) response_code,
294 : (int) ogr.hr.ec);
295 0 : break;
296 : }
297 0 : oph->cb (oph->cb_cls,
298 : &ogr);
299 0 : TALER_MERCHANT_get_private_orders_cancel (oph);
300 : }
301 :
302 :
303 : struct TALER_MERCHANT_GetPrivateOrdersHandle *
304 0 : 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 0 : oph = GNUNET_new (struct TALER_MERCHANT_GetPrivateOrdersHandle);
311 0 : oph->ctx = ctx;
312 0 : oph->base_url = GNUNET_strdup (url);
313 0 : oph->paid = TALER_EXCHANGE_YNA_ALL;
314 0 : oph->refunded = TALER_EXCHANGE_YNA_ALL;
315 0 : oph->wired = TALER_EXCHANGE_YNA_ALL;
316 0 : oph->date = GNUNET_TIME_UNIT_FOREVER_TS;
317 0 : oph->offset = UINT64_MAX;
318 0 : oph->limit = -20;
319 0 : return oph;
320 : }
321 :
322 :
323 : enum GNUNET_GenericReturnValue
324 0 : 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 0 : for (unsigned int i = 0; i < num_options; i++)
330 : {
331 0 : const struct TALER_MERCHANT_GetPrivateOrdersOptionValue *opt =
332 0 : &options[i];
333 :
334 0 : switch (opt->option)
335 : {
336 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_END:
337 0 : return GNUNET_OK;
338 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_PAID:
339 0 : oph->paid = opt->details.paid;
340 0 : break;
341 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_REFUNDED:
342 0 : oph->refunded = opt->details.refunded;
343 0 : break;
344 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_WIRED:
345 0 : oph->wired = opt->details.wired;
346 0 : break;
347 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_LIMIT:
348 0 : oph->limit = opt->details.limit;
349 0 : break;
350 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_OFFSET:
351 0 : oph->offset = opt->details.offset;
352 0 : oph->have_offset = true;
353 0 : break;
354 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_DATE:
355 0 : oph->date = opt->details.date;
356 0 : oph->have_date = true;
357 0 : break;
358 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_TIMEOUT:
359 0 : oph->timeout = opt->details.timeout;
360 0 : break;
361 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SESSION_ID:
362 0 : GNUNET_free (oph->session_id);
363 0 : if (NULL != opt->details.session_id)
364 0 : oph->session_id = GNUNET_strdup (opt->details.session_id);
365 0 : break;
366 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_FULFILLMENT_URL:
367 0 : GNUNET_free (oph->fulfillment_url);
368 0 : if (NULL != opt->details.fulfillment_url)
369 0 : oph->fulfillment_url = GNUNET_strdup (opt->details.fulfillment_url);
370 0 : break;
371 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SUMMARY_FILTER:
372 0 : GNUNET_free (oph->summary_filter);
373 0 : if (NULL != opt->details.summary_filter)
374 0 : oph->summary_filter = GNUNET_strdup (opt->details.summary_filter);
375 0 : break;
376 0 : case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_MAX_AGE:
377 0 : oph->max_age = opt->details.max_age;
378 0 : oph->have_max_age = true;
379 0 : break;
380 0 : default:
381 0 : GNUNET_break (0);
382 0 : return GNUNET_NO;
383 : }
384 : }
385 0 : return GNUNET_OK;
386 : }
387 :
388 :
389 : enum TALER_ErrorCode
390 0 : 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 0 : oph->cb = cb;
399 0 : oph->cb_cls = cb_cls;
400 0 : tms = (unsigned int) (oph->timeout.rel_value_us
401 0 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
402 : {
403 : char dstr[30];
404 0 : char *fec = NULL;
405 0 : char *sid = NULL;
406 0 : 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 0 : GNUNET_snprintf (tbuf,
415 : sizeof (tbuf),
416 : "%u",
417 : tms);
418 0 : if (oph->have_max_age)
419 0 : GNUNET_snprintf (mabuf,
420 : sizeof (mabuf),
421 : "%llu",
422 : (unsigned long long)
423 0 : oph->max_age.rel_value_us);
424 0 : GNUNET_snprintf (dbuf,
425 : sizeof (dbuf),
426 : "%lld",
427 0 : (long long) oph->limit);
428 0 : GNUNET_snprintf (cbuf,
429 : sizeof (cbuf),
430 : "%llu",
431 0 : (unsigned long long) oph->offset);
432 0 : if (NULL != oph->session_id)
433 0 : (void) GNUNET_STRINGS_urlencode (strlen (oph->session_id),
434 0 : oph->session_id,
435 : &sid);
436 0 : if (NULL != oph->fulfillment_url)
437 0 : (void) GNUNET_STRINGS_urlencode (strlen (oph->fulfillment_url),
438 0 : oph->fulfillment_url,
439 : &fec);
440 0 : if (NULL != oph->summary_filter)
441 0 : (void) GNUNET_STRINGS_urlencode (strlen (oph->summary_filter),
442 0 : oph->summary_filter,
443 : &sfilt);
444 0 : GNUNET_snprintf (dstr,
445 : sizeof (dstr),
446 : "%llu",
447 0 : (unsigned long long) GNUNET_TIME_timestamp_to_s (
448 : oph->date));
449 0 : if (oph->limit > 0)
450 : {
451 0 : have_date = oph->have_date
452 0 : && ! GNUNET_TIME_absolute_is_zero (oph->date.abs_time);
453 0 : have_srow = oph->have_offset
454 0 : && (0 != oph->offset);
455 : }
456 : else
457 : {
458 0 : have_date = oph->have_date
459 0 : && ! GNUNET_TIME_absolute_is_never (oph->date.abs_time);
460 0 : have_srow = oph->have_offset
461 0 : && (UINT64_MAX != oph->offset);
462 : }
463 0 : oph->url = TALER_url_join (oph->base_url,
464 : "private/orders",
465 : "paid",
466 0 : (TALER_EXCHANGE_YNA_ALL != oph->paid)
467 0 : ? TALER_yna_to_string (oph->paid)
468 : : NULL,
469 : "refunded",
470 0 : (TALER_EXCHANGE_YNA_ALL != oph->refunded)
471 0 : ? TALER_yna_to_string (oph->refunded)
472 : : NULL,
473 : "wired",
474 0 : (TALER_EXCHANGE_YNA_ALL != oph->wired)
475 0 : ? 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 0 : (-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 0 : oph->have_max_age
501 : ? mabuf
502 : : NULL,
503 : NULL);
504 0 : GNUNET_free (sid);
505 0 : GNUNET_free (fec);
506 0 : GNUNET_free (sfilt);
507 : }
508 0 : if (NULL == oph->url)
509 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
510 0 : eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
511 0 : if (NULL == eh)
512 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
513 0 : if (0 != tms)
514 : {
515 0 : GNUNET_break (CURLE_OK ==
516 : curl_easy_setopt (eh,
517 : CURLOPT_TIMEOUT_MS,
518 : (long) (tms + 100L)));
519 : }
520 0 : oph->job = GNUNET_CURL_job_add (oph->ctx,
521 : eh,
522 : &handle_get_orders_finished,
523 : oph);
524 0 : if (NULL == oph->job)
525 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
526 0 : return TALER_EC_NONE;
527 : }
528 :
529 :
530 : void
531 0 : TALER_MERCHANT_get_private_orders_cancel (
532 : struct TALER_MERCHANT_GetPrivateOrdersHandle *oph)
533 : {
534 0 : if (NULL != oph->job)
535 : {
536 0 : GNUNET_CURL_job_cancel (oph->job);
537 0 : oph->job = NULL;
538 : }
539 0 : GNUNET_free (oph->url);
540 0 : GNUNET_free (oph->session_id);
541 0 : GNUNET_free (oph->fulfillment_url);
542 0 : GNUNET_free (oph->summary_filter);
543 0 : GNUNET_free (oph->base_url);
544 0 : GNUNET_free (oph);
545 0 : }
546 :
547 :
548 : /* end of merchant_api_get-private-orders-new.c */
|