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