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