Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018, 2019, 2020 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_merchant_get_order.c
19 : * @brief Implementation of the GET /private/orders/$ORDER request
20 : * @author Christian Grothoff
21 : * @author Marcello Stanisci
22 : * @author Florian Dold
23 : */
24 : #include "platform.h"
25 : #include <curl/curl.h>
26 : #include <jansson.h>
27 : #include <microhttpd.h> /* just for HTTP status codes */
28 : #include <gnunet/gnunet_util_lib.h>
29 : #include <gnunet/gnunet_curl_lib.h>
30 : #include "taler_merchant_service.h"
31 : #include "merchant_api_curl_defaults.h"
32 : #include <taler/taler_json_lib.h>
33 : #include <taler/taler_signatures.h>
34 :
35 :
36 : /**
37 : * Maximum number of refund details we return.
38 : */
39 : #define MAX_REFUND_DETAILS 1024
40 :
41 : /**
42 : * Maximum number of wire details we return.
43 : */
44 : #define MAX_WIRE_DETAILS 1024
45 :
46 :
47 : /**
48 : * @brief A GET /private/orders/$ORDER handle
49 : */
50 : struct TALER_MERCHANT_OrderMerchantGetHandle
51 : {
52 :
53 : /**
54 : * The url for this request.
55 : */
56 : char *url;
57 :
58 : /**
59 : * Handle for the request.
60 : */
61 : struct GNUNET_CURL_Job *job;
62 :
63 : /**
64 : * Function to call with the result.
65 : */
66 : TALER_MERCHANT_OrderMerchantGetCallback cb;
67 :
68 : /**
69 : * Closure for @a cb.
70 : */
71 : void *cb_cls;
72 :
73 : /**
74 : * Reference to the execution context.
75 : */
76 : struct GNUNET_CURL_Context *ctx;
77 : };
78 :
79 :
80 : /**
81 : * Function called when we're done processing the GET /private/orders/$ORDER
82 : * request and we got an HTTP status of OK and the order was unpaid. Parse
83 : * the response and call the callback.
84 : *
85 : * @param omgh handle for the request
86 : * @param[in,out] osr HTTP response we got
87 : */
88 : static void
89 6 : handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
90 : struct TALER_MERCHANT_OrderStatusResponse *osr)
91 : {
92 : struct GNUNET_JSON_Specification spec[] = {
93 6 : TALER_JSON_spec_amount_any (
94 : "total_amount",
95 : &osr->details.ok.details.unpaid.contract_amount),
96 6 : GNUNET_JSON_spec_mark_optional (
97 : GNUNET_JSON_spec_string (
98 : "already_paid_order_id",
99 : &osr->details.ok.details.unpaid.already_paid_order_id),
100 : NULL),
101 6 : GNUNET_JSON_spec_string (
102 : "taler_pay_uri",
103 : &osr->details.ok.details.unpaid.taler_pay_uri),
104 6 : GNUNET_JSON_spec_string (
105 : "summary",
106 : &osr->details.ok.details.unpaid.summary),
107 6 : GNUNET_JSON_spec_timestamp (
108 : "creation_time",
109 : &osr->details.ok.details.unpaid.creation_time),
110 6 : GNUNET_JSON_spec_end ()
111 : };
112 :
113 6 : if (GNUNET_OK !=
114 6 : GNUNET_JSON_parse (osr->hr.reply,
115 : spec,
116 : NULL, NULL))
117 : {
118 0 : GNUNET_break_op (0);
119 0 : osr->hr.http_status = 0;
120 0 : osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
121 0 : omgh->cb (omgh->cb_cls,
122 : osr);
123 0 : return;
124 : }
125 6 : osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID;
126 6 : omgh->cb (omgh->cb_cls,
127 : osr);
128 : }
129 :
130 :
131 : /**
132 : * Function called when we're done processing the GET /private/orders/$ORDER
133 : * request and we got an HTTP status of OK and the order was claimed but not
134 : * paid. Parse the response and call the callback.
135 : *
136 : * @param omgh handle for the request
137 : * @param[in,out] osr HTTP response we got
138 : */
139 : static void
140 8 : handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
141 : struct TALER_MERCHANT_OrderStatusResponse *osr)
142 : {
143 : struct GNUNET_JSON_Specification spec[] = {
144 8 : GNUNET_JSON_spec_object_const (
145 : "contract_terms",
146 : &osr->details.ok.details.claimed.contract_terms),
147 8 : GNUNET_JSON_spec_end ()
148 : };
149 :
150 8 : if (GNUNET_OK !=
151 8 : GNUNET_JSON_parse (osr->hr.reply,
152 : spec,
153 : NULL, NULL))
154 : {
155 0 : GNUNET_break_op (0);
156 0 : osr->hr.http_status = 0;
157 0 : osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
158 0 : omgh->cb (omgh->cb_cls,
159 : osr);
160 0 : return;
161 : }
162 8 : osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED;
163 8 : omgh->cb (omgh->cb_cls,
164 : osr);
165 : }
166 :
167 :
168 : /**
169 : * Function called when we're done processing the GET /private/orders/$ORDER
170 : * request and we got an HTTP status of OK and the order was paid. Parse
171 : * the response and call the callback.
172 : *
173 : * @param omgh handle for the request
174 : * @param[in,out] osr HTTP response we got
175 : */
176 : static void
177 20 : handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
178 : struct TALER_MERCHANT_OrderStatusResponse *osr)
179 : {
180 : uint32_t hc32;
181 : const json_t *wire_details;
182 : const json_t *refund_details;
183 : struct GNUNET_JSON_Specification spec[] = {
184 20 : GNUNET_JSON_spec_bool ("refunded",
185 : &osr->details.ok.details.paid.refunded),
186 20 : GNUNET_JSON_spec_bool ("refund_pending",
187 : &osr->details.ok.details.paid.refund_pending),
188 20 : GNUNET_JSON_spec_bool ("wired",
189 : &osr->details.ok.details.paid.wired),
190 20 : TALER_JSON_spec_amount_any ("deposit_total",
191 : &osr->details.ok.details.paid.deposit_total),
192 20 : TALER_JSON_spec_ec ("exchange_code",
193 : &osr->details.ok.details.paid.exchange_ec),
194 20 : GNUNET_JSON_spec_uint32 ("exchange_http_status",
195 : &hc32),
196 20 : TALER_JSON_spec_amount_any ("refund_amount",
197 : &osr->details.ok.details.paid.refund_amount),
198 20 : GNUNET_JSON_spec_object_const (
199 : "contract_terms",
200 : &osr->details.ok.details.paid.contract_terms),
201 20 : GNUNET_JSON_spec_array_const ("wire_details",
202 : &wire_details),
203 20 : GNUNET_JSON_spec_array_const ("refund_details",
204 : &refund_details),
205 : /* Only available since **v14** */
206 20 : GNUNET_JSON_spec_mark_optional (
207 : GNUNET_JSON_spec_timestamp ("last_payment",
208 : &osr->details.ok.details.paid.last_payment),
209 : NULL),
210 20 : GNUNET_JSON_spec_end ()
211 : };
212 :
213 20 : if (GNUNET_OK !=
214 20 : GNUNET_JSON_parse (osr->hr.reply,
215 : spec,
216 : NULL, NULL))
217 : {
218 0 : GNUNET_break_op (0);
219 0 : osr->hr.http_status = 0;
220 0 : osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
221 0 : omgh->cb (omgh->cb_cls,
222 : osr);
223 0 : return;
224 : }
225 20 : osr->details.ok.status = TALER_MERCHANT_OSC_PAID;
226 :
227 20 : osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32;
228 : {
229 20 : unsigned int wts_len = (unsigned int) json_array_size (wire_details);
230 20 : unsigned int ref_len = (unsigned int) json_array_size (refund_details);
231 :
232 20 : if ( (json_array_size (wire_details) != (size_t) wts_len) ||
233 : (wts_len > MAX_WIRE_DETAILS) )
234 : {
235 0 : GNUNET_break (0);
236 0 : osr->hr.http_status = 0;
237 0 : osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
238 0 : omgh->cb (omgh->cb_cls,
239 : osr);
240 0 : return;
241 : }
242 20 : if ( (json_array_size (refund_details) != (size_t) ref_len) ||
243 : (ref_len > MAX_REFUND_DETAILS) )
244 : {
245 0 : GNUNET_break (0);
246 0 : osr->hr.http_status = 0;
247 0 : osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
248 0 : omgh->cb (omgh->cb_cls,
249 : osr);
250 0 : return;
251 : }
252 20 : {
253 20 : struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)];
254 20 : struct TALER_MERCHANT_RefundOrderDetail ref[GNUNET_NZL (ref_len)];
255 :
256 28 : for (unsigned int i = 0; i<wts_len; i++)
257 : {
258 8 : struct TALER_MERCHANT_WireTransfer *wt = &wts[i];
259 8 : const json_t *w = json_array_get (wire_details,
260 : i);
261 : struct GNUNET_JSON_Specification ispec[] = {
262 8 : TALER_JSON_spec_web_url ("exchange_url",
263 : &wt->exchange_url),
264 8 : GNUNET_JSON_spec_fixed_auto ("wtid",
265 : &wt->wtid),
266 8 : GNUNET_JSON_spec_timestamp ("execution_time",
267 : &wt->execution_time),
268 8 : TALER_JSON_spec_amount_any ("amount",
269 : &wt->total_amount),
270 8 : GNUNET_JSON_spec_bool ("confirmed",
271 : &wt->confirmed),
272 8 : GNUNET_JSON_spec_end ()
273 : };
274 :
275 8 : if (GNUNET_OK !=
276 8 : GNUNET_JSON_parse (w,
277 : ispec,
278 : NULL, NULL))
279 : {
280 0 : GNUNET_break_op (0);
281 0 : osr->hr.http_status = 0;
282 0 : osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
283 0 : omgh->cb (omgh->cb_cls,
284 : osr);
285 0 : return;
286 : }
287 : }
288 :
289 28 : for (unsigned int i = 0; i<ref_len; i++)
290 : {
291 8 : struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i];
292 8 : const json_t *w = json_array_get (refund_details,
293 : i);
294 : struct GNUNET_JSON_Specification ispec[] = {
295 8 : TALER_JSON_spec_amount_any ("amount",
296 : &ro->refund_amount),
297 8 : GNUNET_JSON_spec_string ("reason",
298 : &ro->reason),
299 8 : GNUNET_JSON_spec_timestamp ("timestamp",
300 : &ro->refund_time),
301 8 : GNUNET_JSON_spec_end ()
302 : };
303 :
304 8 : if (GNUNET_OK !=
305 8 : GNUNET_JSON_parse (w,
306 : ispec,
307 : NULL, NULL))
308 : {
309 0 : GNUNET_break_op (0);
310 0 : osr->hr.http_status = 0;
311 0 : osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
312 0 : omgh->cb (omgh->cb_cls,
313 : osr);
314 0 : return;
315 : }
316 : }
317 :
318 20 : osr->details.ok.details.paid.wts = wts;
319 20 : osr->details.ok.details.paid.wts_len = wts_len;
320 20 : osr->details.ok.details.paid.refunds = ref;
321 20 : osr->details.ok.details.paid.refunds_len = ref_len;
322 20 : omgh->cb (omgh->cb_cls,
323 : osr);
324 : }
325 : }
326 : }
327 :
328 :
329 : /**
330 : * Function called when we're done processing the GET /private/orders/$ORDER
331 : * request.
332 : *
333 : * @param cls the `struct TALER_MERCHANT_OrderMerchantGetHandle`
334 : * @param response_code HTTP response code, 0 on error
335 : * @param response response body, NULL if not in JSON
336 : */
337 : static void
338 34 : handle_merchant_order_get_finished (void *cls,
339 : long response_code,
340 : const void *response)
341 : {
342 34 : struct TALER_MERCHANT_OrderMerchantGetHandle *omgh = cls;
343 34 : const json_t *json = response;
344 : const char *order_status;
345 34 : struct TALER_MERCHANT_OrderStatusResponse osr = {
346 34 : .hr.http_status = (unsigned int) response_code,
347 : .hr.reply = json
348 : };
349 :
350 34 : omgh->job = NULL;
351 34 : switch (response_code)
352 : {
353 34 : case MHD_HTTP_OK:
354 : /* see below */
355 34 : break;
356 0 : case MHD_HTTP_ACCEPTED:
357 : /* see below */
358 0 : omgh->cb (omgh->cb_cls,
359 : &osr);
360 0 : TALER_MERCHANT_merchant_order_get_cancel (omgh);
361 0 : return;
362 0 : case MHD_HTTP_UNAUTHORIZED:
363 0 : osr.hr.ec = TALER_JSON_get_error_code (json);
364 0 : osr.hr.hint = TALER_JSON_get_error_hint (json);
365 0 : omgh->cb (omgh->cb_cls,
366 : &osr);
367 0 : TALER_MERCHANT_merchant_order_get_cancel (omgh);
368 0 : return;
369 0 : case MHD_HTTP_NOT_FOUND:
370 0 : osr.hr.ec = TALER_JSON_get_error_code (json);
371 0 : osr.hr.hint = TALER_JSON_get_error_hint (json);
372 0 : omgh->cb (omgh->cb_cls,
373 : &osr);
374 0 : TALER_MERCHANT_merchant_order_get_cancel (omgh);
375 0 : return;
376 0 : case MHD_HTTP_GATEWAY_TIMEOUT:
377 0 : osr.hr.ec = TALER_JSON_get_error_code (json);
378 0 : osr.hr.hint = TALER_JSON_get_error_hint (json);
379 0 : omgh->cb (omgh->cb_cls,
380 : &osr);
381 0 : TALER_MERCHANT_merchant_order_get_cancel (omgh);
382 0 : return;
383 0 : default:
384 0 : osr.hr.ec = TALER_JSON_get_error_code (json);
385 0 : osr.hr.hint = TALER_JSON_get_error_hint (json);
386 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
387 : "Polling payment failed with HTTP status code %u/%d\n",
388 : (unsigned int) response_code,
389 : (int) osr.hr.ec);
390 0 : GNUNET_break_op (0);
391 0 : omgh->cb (omgh->cb_cls,
392 : &osr);
393 0 : TALER_MERCHANT_merchant_order_get_cancel (omgh);
394 0 : return;
395 : }
396 :
397 34 : order_status = json_string_value (json_object_get (json,
398 : "order_status"));
399 :
400 34 : if (NULL == order_status)
401 : {
402 0 : GNUNET_break_op (0);
403 0 : osr.hr.http_status = 0;
404 0 : osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
405 0 : omgh->cb (omgh->cb_cls,
406 : &osr);
407 0 : TALER_MERCHANT_merchant_order_get_cancel (omgh);
408 0 : return;
409 : }
410 :
411 34 : if (0 == strcmp ("paid",
412 : order_status))
413 : {
414 20 : handle_paid (omgh,
415 : &osr);
416 : }
417 14 : else if (0 == strcmp ("claimed",
418 : order_status))
419 : {
420 8 : handle_claimed (omgh,
421 : &osr);
422 : }
423 6 : else if (0 == strcmp ("unpaid",
424 : order_status))
425 : {
426 6 : handle_unpaid (omgh,
427 : &osr);
428 : }
429 : else
430 : {
431 0 : GNUNET_break_op (0);
432 0 : osr.hr.http_status = 0;
433 0 : osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
434 0 : omgh->cb (omgh->cb_cls,
435 : &osr);
436 : }
437 34 : TALER_MERCHANT_merchant_order_get_cancel (omgh);
438 : }
439 :
440 :
441 : struct TALER_MERCHANT_OrderMerchantGetHandle *
442 34 : TALER_MERCHANT_merchant_order_get (
443 : struct GNUNET_CURL_Context *ctx,
444 : const char *backend_url,
445 : const char *order_id,
446 : const char *session_id,
447 : struct GNUNET_TIME_Relative timeout,
448 : TALER_MERCHANT_OrderMerchantGetCallback cb,
449 : void *cb_cls)
450 : {
451 : struct TALER_MERCHANT_OrderMerchantGetHandle *omgh;
452 : unsigned int tms;
453 :
454 68 : tms = (unsigned int) (timeout.rel_value_us
455 34 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
456 34 : omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle);
457 34 : omgh->ctx = ctx;
458 34 : omgh->cb = cb;
459 34 : omgh->cb_cls = cb_cls;
460 : {
461 : char *path;
462 : char timeout_ms[32];
463 :
464 34 : GNUNET_snprintf (timeout_ms,
465 : sizeof (timeout_ms),
466 : "%u",
467 : tms);
468 34 : GNUNET_asprintf (&path,
469 : "private/orders/%s",
470 : order_id);
471 34 : omgh->url = TALER_url_join (backend_url,
472 : path,
473 : "session_id", session_id,
474 : "timeout_ms", (0 != tms) ? timeout_ms : NULL,
475 : NULL);
476 34 : GNUNET_free (path);
477 : }
478 34 : if (NULL == omgh->url)
479 : {
480 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
481 : "Could not construct request URL.\n");
482 0 : GNUNET_free (omgh);
483 0 : return NULL;
484 : }
485 :
486 : {
487 : CURL *eh;
488 :
489 34 : eh = TALER_MERCHANT_curl_easy_get_ (omgh->url);
490 34 : if (NULL == eh)
491 : {
492 0 : GNUNET_break (0);
493 0 : GNUNET_free (omgh->url);
494 0 : GNUNET_free (omgh);
495 0 : return NULL;
496 : }
497 34 : if (0 != tms)
498 : {
499 4 : GNUNET_break (CURLE_OK ==
500 : curl_easy_setopt (eh,
501 : CURLOPT_TIMEOUT_MS,
502 : (long) (tms + 100L)));
503 : }
504 :
505 34 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
506 : "Getting order status from %s\n",
507 : omgh->url);
508 34 : if (NULL == (omgh->job =
509 34 : GNUNET_CURL_job_add (ctx,
510 : eh,
511 : &handle_merchant_order_get_finished,
512 : omgh)))
513 : {
514 0 : GNUNET_break (0);
515 0 : GNUNET_free (omgh->url);
516 0 : GNUNET_free (omgh);
517 0 : return NULL;
518 : }
519 : }
520 34 : return omgh;
521 : }
522 :
523 :
524 : void
525 34 : TALER_MERCHANT_merchant_order_get_cancel (
526 : struct TALER_MERCHANT_OrderMerchantGetHandle *omgh)
527 : {
528 34 : if (NULL != omgh->job)
529 : {
530 0 : GNUNET_CURL_job_cancel (omgh->job);
531 0 : omgh->job = NULL;
532 : }
533 34 : GNUNET_free (omgh->url);
534 34 : GNUNET_free (omgh);
535 34 : }
536 :
537 :
538 : /* end of merchant_api_merchant_get_order.c */
|