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