Line data Source code
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 9 : parse_orders (const json_t *ia,
81 : struct TALER_MERCHANT_OrdersGetResponse *ogr,
82 : struct TALER_MERCHANT_OrdersGetHandle *ogh)
83 : {
84 9 : unsigned int oes_len = (unsigned int) json_array_size (ia);
85 :
86 9 : if ( (json_array_size (ia) != (size_t) oes_len) ||
87 : (oes_len > MAX_ORDERS) )
88 : {
89 0 : GNUNET_break (0);
90 0 : return GNUNET_SYSERR;
91 : }
92 9 : {
93 9 : struct TALER_MERCHANT_OrderEntry oes[GNUNET_NZL (oes_len)];
94 : size_t index;
95 : json_t *value;
96 :
97 9 : memset (oes,
98 : 0,
99 : sizeof (oes));
100 17 : json_array_foreach (ia, index, value) {
101 8 : struct TALER_MERCHANT_OrderEntry *ie = &oes[index];
102 : struct GNUNET_JSON_Specification spec[] = {
103 8 : GNUNET_JSON_spec_string ("order_id",
104 : &ie->order_id),
105 8 : GNUNET_JSON_spec_timestamp ("timestamp",
106 : &ie->timestamp),
107 8 : GNUNET_JSON_spec_uint64 ("row_id",
108 : &ie->order_serial),
109 8 : TALER_JSON_spec_amount_any ("amount",
110 : &ie->amount),
111 8 : GNUNET_JSON_spec_mark_optional (
112 : TALER_JSON_spec_amount_any ("refund_amount",
113 : &ie->refund_amount),
114 : NULL),
115 8 : GNUNET_JSON_spec_mark_optional (
116 : TALER_JSON_spec_amount_any ("pending_refund_amount",
117 : &ie->pending_refund_amount),
118 : NULL),
119 8 : GNUNET_JSON_spec_string ("summary",
120 : &ie->summary),
121 8 : GNUNET_JSON_spec_bool ("refundable",
122 : &ie->refundable),
123 8 : GNUNET_JSON_spec_bool ("paid",
124 : &ie->paid),
125 8 : GNUNET_JSON_spec_end ()
126 : };
127 :
128 8 : if (GNUNET_OK !=
129 8 : GNUNET_JSON_parse (value,
130 : spec,
131 : NULL, NULL))
132 : {
133 0 : GNUNET_break_op (0);
134 0 : return GNUNET_SYSERR;
135 : }
136 : }
137 9 : ogr->details.ok.orders_length = oes_len;
138 9 : ogr->details.ok.orders = oes;
139 9 : ogh->cb (ogh->cb_cls,
140 : ogr);
141 9 : ogh->cb = NULL; /* just to be sure */
142 : }
143 9 : 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 9 : handle_get_orders_finished (void *cls,
157 : long response_code,
158 : const void *response)
159 : {
160 9 : struct TALER_MERCHANT_OrdersGetHandle *ogh = cls;
161 9 : const json_t *json = response;
162 9 : struct TALER_MERCHANT_OrdersGetResponse ogr = {
163 9 : .hr.http_status = (unsigned int) response_code,
164 : .hr.reply = json
165 : };
166 :
167 9 : ogh->job = NULL;
168 9 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
169 : "Got /orders response with status code %u\n",
170 : (unsigned int) response_code);
171 9 : switch (response_code)
172 : {
173 9 : case MHD_HTTP_OK:
174 : {
175 : const json_t *orders;
176 : struct GNUNET_JSON_Specification spec[] = {
177 9 : GNUNET_JSON_spec_array_const ("orders",
178 : &orders),
179 9 : GNUNET_JSON_spec_end ()
180 : };
181 :
182 9 : if (GNUNET_OK !=
183 9 : GNUNET_JSON_parse (json,
184 : spec,
185 : NULL, NULL))
186 : {
187 0 : ogr.hr.http_status = 0;
188 0 : ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
189 0 : break;
190 : }
191 9 : if (GNUNET_OK ==
192 9 : parse_orders (orders,
193 : &ogr,
194 : ogh))
195 : {
196 9 : TALER_MERCHANT_orders_get_cancel (ogh);
197 9 : return;
198 : }
199 0 : ogr.hr.http_status = 0;
200 0 : ogr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
201 0 : break;
202 : }
203 0 : case MHD_HTTP_UNAUTHORIZED:
204 0 : ogr.hr.ec = TALER_JSON_get_error_code (json);
205 0 : ogr.hr.hint = TALER_JSON_get_error_hint (json);
206 : /* Nothing really to verify, merchant says we need to authenticate. */
207 0 : break;
208 0 : case MHD_HTTP_NOT_FOUND:
209 0 : ogr.hr.ec = TALER_JSON_get_error_code (json);
210 0 : ogr.hr.hint = TALER_JSON_get_error_hint (json);
211 0 : break;
212 0 : default:
213 : /* unexpected response code */
214 0 : ogr.hr.ec = TALER_JSON_get_error_code (json);
215 0 : ogr.hr.hint = TALER_JSON_get_error_hint (json);
216 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
217 : "Unexpected response code %u/%d\n",
218 : (unsigned int) response_code,
219 : (int) ogr.hr.ec);
220 0 : break;
221 : }
222 0 : ogh->cb (ogh->cb_cls,
223 : &ogr);
224 0 : TALER_MERCHANT_orders_get_cancel (ogh);
225 : }
226 :
227 :
228 : struct TALER_MERCHANT_OrdersGetHandle *
229 7 : 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 14 : 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 7 : GNUNET_TIME_UNIT_FOREVER_TS,
241 : UINT64_MAX,
242 : -20, /* default is most recent 20 entries */
243 7 : GNUNET_TIME_UNIT_ZERO,
244 : cb,
245 : cb_cls);
246 : }
247 :
248 :
249 : struct TALER_MERCHANT_OrdersGetHandle *
250 9 : 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 9 : 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 9 : 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 18 : unsigned int tms = timeout.rel_value_us
299 9 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
300 :
301 9 : GNUNET_assert (NULL != backend_url);
302 9 : if ( (delta > MAX_ORDERS) ||
303 : (delta < -MAX_ORDERS) )
304 : {
305 0 : GNUNET_break (0);
306 0 : return NULL;
307 : }
308 9 : if (0 == delta)
309 : {
310 0 : GNUNET_break (0);
311 0 : return NULL;
312 : }
313 9 : ogh = GNUNET_new (struct TALER_MERCHANT_OrdersGetHandle);
314 9 : ogh->ctx = ctx;
315 9 : ogh->cb = cb;
316 9 : ogh->cb_cls = cb_cls;
317 :
318 : /* build ogh->url with the various optional arguments */
319 : {
320 : char dstr[30];
321 9 : char *fec = NULL;
322 9 : 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 9 : GNUNET_snprintf (tbuf,
330 : sizeof (tbuf),
331 : "%u",
332 : tms);
333 9 : GNUNET_snprintf (dbuf,
334 : sizeof (dbuf),
335 : "%lld",
336 : (long long) delta);
337 9 : GNUNET_snprintf (cbuf,
338 : sizeof (cbuf),
339 : "%llu",
340 : (unsigned long long) start_row);
341 9 : if (NULL != session_id)
342 0 : (void) GNUNET_STRINGS_urlencode (strlen (session_id),
343 : session_id,
344 : &sid);
345 9 : if (NULL != fulfillment_url)
346 0 : (void) GNUNET_STRINGS_urlencode (strlen (fulfillment_url),
347 : fulfillment_url,
348 : &fec);
349 9 : GNUNET_snprintf (dstr,
350 : sizeof (dstr),
351 : "%llu",
352 9 : (unsigned long long) GNUNET_TIME_timestamp_to_s (date));
353 9 : if (delta > 0)
354 : {
355 2 : have_date = ! GNUNET_TIME_absolute_is_zero (date.abs_time);
356 2 : have_srow = (0 != start_row);
357 : }
358 : else
359 : {
360 7 : have_date = ! GNUNET_TIME_absolute_is_never (date.abs_time);
361 7 : have_srow = (UINT64_MAX != start_row);
362 : }
363 9 : ogh->url = TALER_url_join (backend_url,
364 : "private/orders",
365 : "paid",
366 : (TALER_EXCHANGE_YNA_ALL != paid)
367 0 : ? TALER_yna_to_string (paid)
368 : : NULL,
369 : "refunded",
370 : (TALER_EXCHANGE_YNA_ALL != refunded)
371 0 : ? TALER_yna_to_string (refunded)
372 : : NULL,
373 : "wired",
374 : (TALER_EXCHANGE_YNA_ALL != wired)
375 0 : ? 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 9 : GNUNET_free (sid);
399 9 : GNUNET_free (fec);
400 : }
401 9 : if (NULL == ogh->url)
402 : {
403 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
404 : "Could not construct request URL.\n");
405 0 : GNUNET_free (ogh);
406 0 : return NULL;
407 : }
408 9 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
409 : "Requesting URL '%s'\n",
410 : ogh->url);
411 9 : eh = TALER_MERCHANT_curl_easy_get_ (ogh->url);
412 9 : if (NULL == eh)
413 : {
414 0 : GNUNET_break (0);
415 0 : GNUNET_free (ogh->url);
416 0 : GNUNET_free (ogh);
417 0 : return NULL;
418 : }
419 9 : if (0 != tms)
420 : {
421 2 : GNUNET_break (CURLE_OK ==
422 : curl_easy_setopt (eh,
423 : CURLOPT_TIMEOUT_MS,
424 : (long) (tms + 100L)));
425 : }
426 9 : ogh->job = GNUNET_CURL_job_add (ctx,
427 : eh,
428 : &handle_get_orders_finished,
429 : ogh);
430 9 : return ogh;
431 : }
432 :
433 :
434 : void
435 9 : TALER_MERCHANT_orders_get_cancel (
436 : struct TALER_MERCHANT_OrdersGetHandle *ogh)
437 : {
438 9 : if (NULL != ogh->job)
439 0 : GNUNET_CURL_job_cancel (ogh->job);
440 9 : GNUNET_free (ogh->url);
441 9 : GNUNET_free (ogh);
442 9 : }
|