Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free Software
7 : Foundation; either version 3, 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 General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-merchant-httpd_get-orders-ID.c
18 : * @brief implementation of GET /orders/$ID
19 : * @author Marcello Stanisci
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <gnunet/gnunet_uri_lib.h>
25 : #include <taler/taler_signatures.h>
26 : #include <taler/taler_dbevents.h>
27 : #include <taler/taler_json_lib.h>
28 : #include <taler/taler_templating_lib.h>
29 : #include <taler/taler_exchange_service.h>
30 : #include "taler-merchant-httpd_exchanges.h"
31 : #include "taler-merchant-httpd_get-orders-ID.h"
32 : #include "taler-merchant-httpd_mhd.h"
33 : #include "taler-merchant-httpd_qr.h"
34 :
35 : /**
36 : * How often do we retry DB transactions on serialization failures?
37 : */
38 : #define MAX_RETRIES 5
39 :
40 :
41 : /**
42 : * Context for the operation.
43 : */
44 : struct GetOrderData
45 : {
46 :
47 : /**
48 : * Hashed version of contract terms. All zeros if not provided.
49 : */
50 : struct TALER_PrivateContractHashP h_contract_terms;
51 :
52 : /**
53 : * Claim token used for access control. All zeros if not provided.
54 : */
55 : struct TALER_ClaimTokenP claim_token;
56 :
57 : /**
58 : * DLL of (suspended) requests.
59 : */
60 : struct GetOrderData *next;
61 :
62 : /**
63 : * DLL of (suspended) requests.
64 : */
65 : struct GetOrderData *prev;
66 :
67 : /**
68 : * Context of the request.
69 : */
70 : struct TMH_HandlerContext *hc;
71 :
72 : /**
73 : * Entry in the #resume_timeout_heap for this check payment, if we are
74 : * suspended.
75 : */
76 : struct TMH_SuspendedConnection sc;
77 :
78 : /**
79 : * Database event we are waiting on to be resuming.
80 : */
81 : struct GNUNET_DB_EventHandler *pay_eh;
82 :
83 : /**
84 : * Database event we are waiting on to be resuming.
85 : */
86 : struct GNUNET_DB_EventHandler *refund_eh;
87 :
88 : /**
89 : * Which merchant instance is this for?
90 : */
91 : struct MerchantInstance *mi;
92 :
93 : /**
94 : * order ID for the payment
95 : */
96 : const char *order_id;
97 :
98 : /**
99 : * Where to get the contract
100 : */
101 : const char *contract_url;
102 :
103 : /**
104 : * fulfillment URL of the contract (valid as long as @e contract_terms is
105 : * valid; but can also be NULL if the contract_terms does not come with
106 : * a fulfillment URL).
107 : */
108 : const char *fulfillment_url;
109 :
110 : /**
111 : * session of the client
112 : */
113 : const char *session_id;
114 :
115 : /**
116 : * Contract terms of the payment we are checking. NULL when they
117 : * are not (yet) known.
118 : */
119 : json_t *contract_terms;
120 :
121 : /**
122 : * Total refunds granted for this payment. Only initialized
123 : * if @e refunded is set to true.
124 : */
125 : struct TALER_Amount refund_amount;
126 :
127 : /**
128 : * Total refunds already collected.
129 : * if @e refunded is set to true.
130 : */
131 : struct TALER_Amount refund_taken;
132 :
133 : /**
134 : * Return code: #TALER_EC_NONE if successful.
135 : */
136 : enum TALER_ErrorCode ec;
137 :
138 : /**
139 : * Did we suspend @a connection and are thus in
140 : * the #god_head DLL (#GNUNET_YES). Set to
141 : * #GNUNET_NO if we are not suspended, and to
142 : * #GNUNET_SYSERR if we should close the connection
143 : * without a response due to shutdown.
144 : */
145 : enum GNUNET_GenericReturnValue suspended;
146 :
147 : /**
148 : * Set to true if we are dealing with a claimed order
149 : * (and thus @e h_contract_terms is set, otherwise certain
150 : * DB queries will not work).
151 : */
152 : bool claimed;
153 :
154 : /**
155 : * Set to true if this payment has been refunded and
156 : * @e refund_amount is initialized.
157 : */
158 : bool refunded;
159 :
160 : /**
161 : * Set to true if a refund is still available for the
162 : * wallet for this payment.
163 : * @deprecated: true if refund_taken < refund_amount
164 : */
165 : bool refund_pending;
166 :
167 : /**
168 : * Set to true if the client requested HTML, otherwise we generate JSON.
169 : */
170 : bool generate_html;
171 :
172 : };
173 :
174 :
175 : /**
176 : * Head of DLL of (suspended) requests.
177 : */
178 : static struct GetOrderData *god_head;
179 :
180 : /**
181 : * Tail of DLL of (suspended) requests.
182 : */
183 : static struct GetOrderData *god_tail;
184 :
185 :
186 : void
187 0 : TMH_force_wallet_get_order_resume (void)
188 : {
189 : struct GetOrderData *god;
190 :
191 0 : while (NULL != (god = god_head))
192 : {
193 0 : GNUNET_CONTAINER_DLL_remove (god_head,
194 : god_tail,
195 : god);
196 0 : GNUNET_assert (god->suspended);
197 0 : god->suspended = GNUNET_SYSERR;
198 0 : MHD_resume_connection (god->sc.con);
199 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
200 : }
201 0 : }
202 :
203 :
204 : /**
205 : * We have received a trigger from the database
206 : * that we should (possibly) resume the request.
207 : *
208 : * @param cls a `struct GetOrderData` to resume
209 : * @param extra string encoding refund amount (or NULL)
210 : * @param extra_size number of bytes in @a extra
211 : */
212 : static void
213 0 : resume_by_event (void *cls,
214 : const void *extra,
215 : size_t extra_size)
216 : {
217 0 : struct GetOrderData *god = cls;
218 : struct GNUNET_AsyncScopeSave old;
219 :
220 0 : GNUNET_async_scope_enter (&god->hc->async_scope_id,
221 : &old);
222 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
223 : "Received event for %s with argument `%.*s`\n",
224 : god->order_id,
225 : (int) extra_size,
226 : (const char *) extra);
227 0 : if (! god->suspended)
228 : {
229 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
230 : "Not suspended, ignoring event\n");
231 0 : GNUNET_async_scope_restore (&old);
232 0 : return; /* duplicate event is possible */
233 : }
234 0 : if (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) &&
235 0 : god->sc.awaiting_refund)
236 : {
237 : char *as;
238 : struct TALER_Amount a;
239 :
240 0 : if (0 == extra_size)
241 : {
242 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
243 : "No amount given, but need refund above threshold\n");
244 0 : GNUNET_async_scope_restore (&old);
245 0 : return; /* not relevant */
246 : }
247 0 : as = GNUNET_strndup (extra,
248 : extra_size);
249 0 : if (GNUNET_OK !=
250 0 : TALER_string_to_amount (as,
251 : &a))
252 : {
253 0 : GNUNET_break (0);
254 0 : GNUNET_async_scope_restore (&old);
255 0 : return;
256 : }
257 0 : if (GNUNET_OK !=
258 0 : TALER_amount_cmp_currency (&god->sc.refund_expected,
259 : &a))
260 : {
261 0 : GNUNET_break (0);
262 0 : GNUNET_async_scope_restore (&old);
263 0 : return; /* bad currency!? */
264 : }
265 0 : if (1 == TALER_amount_cmp (&god->sc.refund_expected,
266 : &a))
267 : {
268 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
269 : "Amount too small to trigger resuming\n");
270 0 : GNUNET_async_scope_restore (&old);
271 0 : return; /* refund too small */
272 : }
273 : }
274 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
275 : "Resuming (%d/%d) by event with argument `%.*s`\n",
276 : (int) GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout),
277 : god->sc.awaiting_refund,
278 : (int) extra_size,
279 : (const char *) extra);
280 0 : god->suspended = GNUNET_NO;
281 0 : GNUNET_CONTAINER_DLL_remove (god_head,
282 : god_tail,
283 : god);
284 0 : MHD_resume_connection (god->sc.con);
285 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
286 0 : GNUNET_async_scope_restore (&old);
287 : }
288 :
289 :
290 : /**
291 : * Suspend this @a god until the trigger is satisfied.
292 : *
293 : * @param god request to suspend
294 : */
295 : static void
296 0 : suspend_god (struct GetOrderData *god)
297 : {
298 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
299 : "Suspending GET /orders/%s\n",
300 : god->order_id);
301 0 : if (NULL != god->contract_terms)
302 : {
303 0 : json_decref (god->contract_terms);
304 0 : god->fulfillment_url = NULL;
305 0 : god->contract_terms = NULL;
306 : }
307 0 : GNUNET_assert (! god->suspended);
308 0 : god->suspended = GNUNET_YES;
309 0 : GNUNET_CONTAINER_DLL_insert (god_head,
310 : god_tail,
311 : god);
312 0 : MHD_suspend_connection (god->sc.con);
313 0 : }
314 :
315 :
316 : /**
317 : * Create a taler://refund/ URI for the given @a con and @a order_id
318 : * and @a instance_id.
319 : *
320 : * @param merchant_base_url URL to take host and path from;
321 : * we cannot take it from the MHD connection as a browser
322 : * may have changed 'http' to 'https' and we MUST be consistent
323 : * with what the merchant's frontend used initially
324 : * @param order_id the order id
325 : * @return corresponding taler://refund/ URI, or NULL on missing "host"
326 : */
327 : static char *
328 0 : make_taler_refund_uri (const char *merchant_base_url,
329 : const char *order_id)
330 : {
331 0 : struct GNUNET_Buffer buf = { 0 };
332 : char *url;
333 : struct GNUNET_Uri uri;
334 :
335 0 : url = GNUNET_strdup (merchant_base_url);
336 0 : if (-1 == GNUNET_uri_parse (&uri,
337 : url))
338 : {
339 0 : GNUNET_break (0);
340 0 : GNUNET_free (url);
341 0 : return NULL;
342 : }
343 0 : GNUNET_assert (NULL != order_id);
344 0 : GNUNET_buffer_write_str (&buf,
345 : "taler");
346 0 : if (0 == strcasecmp ("http",
347 0 : uri.scheme))
348 0 : GNUNET_buffer_write_str (&buf,
349 : "+http");
350 0 : GNUNET_buffer_write_str (&buf,
351 : "://refund/");
352 0 : GNUNET_buffer_write_str (&buf,
353 0 : uri.host);
354 0 : if (0 != uri.port)
355 0 : GNUNET_buffer_write_fstr (&buf,
356 : ":%u",
357 0 : (unsigned int) uri.port);
358 0 : if (NULL != uri.path)
359 0 : GNUNET_buffer_write_path (&buf,
360 0 : uri.path);
361 0 : GNUNET_buffer_write_path (&buf,
362 : order_id);
363 0 : GNUNET_buffer_write_path (&buf,
364 : ""); // Trailing slash
365 0 : GNUNET_free (url);
366 0 : return GNUNET_buffer_reap_str (&buf);
367 : }
368 :
369 :
370 : char *
371 0 : TMH_make_order_status_url (struct MHD_Connection *con,
372 : const char *order_id,
373 : const char *session_id,
374 : const char *instance_id,
375 : struct TALER_ClaimTokenP *claim_token,
376 : struct TALER_PrivateContractHashP *h_contract)
377 : {
378 : const char *host;
379 : const char *forwarded_host;
380 : const char *uri_path;
381 0 : struct GNUNET_Buffer buf = { 0 };
382 : /* Number of query parameters written so far */
383 0 : unsigned int num_qp = 0;
384 :
385 0 : host = MHD_lookup_connection_value (con,
386 : MHD_HEADER_KIND,
387 : MHD_HTTP_HEADER_HOST);
388 0 : forwarded_host = MHD_lookup_connection_value (con,
389 : MHD_HEADER_KIND,
390 : "X-Forwarded-Host");
391 0 : uri_path = MHD_lookup_connection_value (con,
392 : MHD_HEADER_KIND,
393 : "X-Forwarded-Prefix");
394 0 : if (NULL != forwarded_host)
395 0 : host = forwarded_host;
396 0 : if (NULL == host)
397 : {
398 0 : GNUNET_break (0);
399 0 : return NULL;
400 : }
401 0 : if (NULL != strchr (host, '/'))
402 : {
403 0 : GNUNET_break_op (0);
404 0 : return NULL;
405 : }
406 0 : GNUNET_assert (NULL != instance_id);
407 0 : GNUNET_assert (NULL != order_id);
408 :
409 0 : if (GNUNET_NO == TALER_mhd_is_https (con))
410 0 : GNUNET_buffer_write_str (&buf,
411 : "http://");
412 : else
413 0 : GNUNET_buffer_write_str (&buf,
414 : "https://");
415 0 : GNUNET_buffer_write_str (&buf,
416 : host);
417 0 : if (NULL != uri_path)
418 0 : GNUNET_buffer_write_path (&buf,
419 : uri_path);
420 0 : if (0 != strcmp ("default",
421 : instance_id))
422 : {
423 0 : GNUNET_buffer_write_path (&buf,
424 : "instances");
425 0 : GNUNET_buffer_write_path (&buf,
426 : instance_id);
427 : }
428 0 : GNUNET_buffer_write_path (&buf,
429 : "/orders");
430 0 : GNUNET_buffer_write_path (&buf,
431 : order_id);
432 0 : if ((NULL != claim_token) &&
433 0 : (GNUNET_NO == GNUNET_is_zero (claim_token)))
434 : {
435 : /* 'token=' for human readability */
436 0 : GNUNET_buffer_write_str (&buf,
437 : "?token=");
438 0 : GNUNET_buffer_write_data_encoded (&buf,
439 : (char *) claim_token,
440 : sizeof (*claim_token));
441 0 : num_qp++;
442 : }
443 :
444 0 : if (NULL != session_id)
445 : {
446 0 : if (num_qp > 0)
447 0 : GNUNET_buffer_write_str (&buf,
448 : "&session_id=");
449 : else
450 0 : GNUNET_buffer_write_str (&buf,
451 : "?session_id=");
452 0 : GNUNET_buffer_write_str (&buf,
453 : session_id);
454 0 : num_qp++;
455 : }
456 :
457 0 : if (NULL != h_contract)
458 : {
459 0 : if (num_qp > 0)
460 0 : GNUNET_buffer_write_str (&buf,
461 : "&h_contract=");
462 : else
463 0 : GNUNET_buffer_write_str (&buf,
464 : "?h_contract=");
465 0 : GNUNET_buffer_write_data_encoded (&buf,
466 : (char *) h_contract,
467 : sizeof (*h_contract));
468 : }
469 :
470 0 : return GNUNET_buffer_reap_str (&buf);
471 : }
472 :
473 :
474 : char *
475 0 : TMH_make_taler_pay_uri (struct MHD_Connection *con,
476 : const char *order_id,
477 : const char *session_id,
478 : const char *instance_id,
479 : struct TALER_ClaimTokenP *claim_token)
480 : {
481 : const char *host;
482 : const char *forwarded_host;
483 : const char *uri_path;
484 0 : struct GNUNET_Buffer buf = { 0 };
485 :
486 0 : host = MHD_lookup_connection_value (con,
487 : MHD_HEADER_KIND,
488 : MHD_HTTP_HEADER_HOST);
489 0 : forwarded_host = MHD_lookup_connection_value (con,
490 : MHD_HEADER_KIND,
491 : "X-Forwarded-Host");
492 0 : uri_path = MHD_lookup_connection_value (con,
493 : MHD_HEADER_KIND,
494 : "X-Forwarded-Prefix");
495 0 : if (NULL != forwarded_host)
496 0 : host = forwarded_host;
497 0 : if (NULL == host)
498 : {
499 0 : GNUNET_break (0);
500 0 : return NULL;
501 : }
502 0 : if (NULL != strchr (host, '/'))
503 : {
504 0 : GNUNET_break_op (0);
505 0 : return NULL;
506 : }
507 0 : GNUNET_assert (NULL != instance_id);
508 0 : GNUNET_assert (NULL != order_id);
509 0 : GNUNET_buffer_write_str (&buf,
510 : "taler");
511 0 : if (GNUNET_NO == TALER_mhd_is_https (con))
512 0 : GNUNET_buffer_write_str (&buf,
513 : "+http");
514 0 : GNUNET_buffer_write_str (&buf,
515 : "://pay/");
516 0 : GNUNET_buffer_write_str (&buf,
517 : host);
518 0 : if (NULL != uri_path)
519 0 : GNUNET_buffer_write_path (&buf,
520 : uri_path);
521 0 : if (0 != strcmp ("default",
522 : instance_id))
523 : {
524 0 : GNUNET_buffer_write_path (&buf,
525 : "instances");
526 0 : GNUNET_buffer_write_path (&buf,
527 : instance_id);
528 : }
529 0 : GNUNET_buffer_write_path (&buf,
530 : order_id);
531 0 : GNUNET_buffer_write_path (&buf,
532 : (session_id == NULL) ? "" : session_id);
533 0 : if ((NULL != claim_token) &&
534 0 : (GNUNET_NO == GNUNET_is_zero (claim_token)))
535 : {
536 : /* Just 'c=' because this goes into QR
537 : codes, so this is more compact. */
538 0 : GNUNET_buffer_write_str (&buf,
539 : "?c=");
540 0 : GNUNET_buffer_write_data_encoded (&buf,
541 : (char *) claim_token,
542 : sizeof (struct TALER_ClaimTokenP));
543 : }
544 :
545 0 : return GNUNET_buffer_reap_str (&buf);
546 : }
547 :
548 :
549 : /**
550 : * Return the order summary of the contract of @a god in the
551 : * preferred language of the HTTP client.
552 : *
553 : * @param god order to extract summary from
554 : * @return dummy error message summary if no summary was provided in the contract
555 : */
556 : static const char *
557 0 : get_order_summary (const struct GetOrderData *god)
558 : {
559 : const char *language_pattern;
560 : const char *ret;
561 :
562 0 : language_pattern = MHD_lookup_connection_value (god->sc.con,
563 : MHD_HEADER_KIND,
564 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
565 0 : if (NULL == language_pattern)
566 0 : language_pattern = "en";
567 0 : ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms,
568 : language_pattern,
569 : "summary"));
570 0 : if (NULL == ret)
571 : {
572 : /* Upon order creation (and insertion into the database), the presence
573 : of a summary should have been checked. So if we get here, someone
574 : did something fishy to our database... */
575 0 : GNUNET_break (0);
576 0 : ret = "<bug: no summary>";
577 : }
578 0 : return ret;
579 : }
580 :
581 :
582 : /**
583 : * The client did not yet pay, send it the payment request.
584 : *
585 : * @param god check pay request context
586 : * @param already_paid_order_id if for the fulfillment URI there is
587 : * already a paid order, this is the order ID to redirect
588 : * the wallet to; NULL if not applicable
589 : * @return #MHD_YES on success
590 : */
591 : static MHD_RESULT
592 0 : send_pay_request (struct GetOrderData *god,
593 : const char *already_paid_order_id)
594 : {
595 : MHD_RESULT ret;
596 : char *taler_pay_uri;
597 : char *order_status_url;
598 : struct GNUNET_TIME_Relative remaining;
599 :
600 0 : remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
601 0 : if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
602 : (NULL == already_paid_order_id) )
603 : {
604 : /* long polling: do not queue a response, suspend connection instead */
605 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
606 : "Suspending request: long polling for payment\n");
607 0 : suspend_god (god);
608 0 : return MHD_YES;
609 : }
610 :
611 : /* Check if resource_id has been paid for in the same session
612 : * with another order_id.
613 : */
614 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
615 : "Sending payment request\n");
616 0 : taler_pay_uri = TMH_make_taler_pay_uri (god->sc.con,
617 : god->order_id,
618 : god->session_id,
619 0 : god->hc->instance->settings.id,
620 : &god->claim_token);
621 0 : order_status_url = TMH_make_order_status_url (god->sc.con,
622 : god->order_id,
623 : god->session_id,
624 0 : god->hc->instance->settings.id,
625 : &god->claim_token,
626 : NULL);
627 0 : if ( (NULL == taler_pay_uri) ||
628 : (NULL == order_status_url) )
629 : {
630 0 : GNUNET_break_op (0);
631 0 : GNUNET_free (taler_pay_uri);
632 0 : GNUNET_free (order_status_url);
633 0 : return TALER_MHD_reply_with_error (god->sc.con,
634 : MHD_HTTP_BAD_REQUEST,
635 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
636 : "host");
637 : }
638 0 : if (god->generate_html)
639 : {
640 0 : if (NULL != already_paid_order_id)
641 : {
642 : struct MHD_Response *reply;
643 :
644 0 : GNUNET_assert (NULL != god->fulfillment_url);
645 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
646 : "Redirecting to already paid order %s via fulfillment URL %s\n",
647 : already_paid_order_id,
648 : god->fulfillment_url);
649 0 : reply = MHD_create_response_from_buffer (0,
650 : NULL,
651 : MHD_RESPMEM_PERSISTENT);
652 0 : if (NULL == reply)
653 : {
654 0 : GNUNET_break (0);
655 0 : return MHD_NO;
656 : }
657 0 : GNUNET_break (MHD_YES ==
658 : MHD_add_response_header (reply,
659 : MHD_HTTP_HEADER_LOCATION,
660 : god->fulfillment_url));
661 : {
662 : MHD_RESULT ret;
663 :
664 0 : ret = MHD_queue_response (god->sc.con,
665 : MHD_HTTP_FOUND,
666 : reply);
667 0 : MHD_destroy_response (reply);
668 0 : return ret;
669 : }
670 : }
671 :
672 : {
673 : char *qr;
674 :
675 0 : qr = TMH_create_qrcode (taler_pay_uri);
676 0 : if (NULL == qr)
677 : {
678 0 : GNUNET_break (0);
679 0 : return MHD_NO;
680 : }
681 : {
682 : enum GNUNET_GenericReturnValue res;
683 : json_t *context;
684 :
685 0 : context = GNUNET_JSON_PACK (
686 : GNUNET_JSON_pack_string ("taler_pay_uri",
687 : taler_pay_uri),
688 : GNUNET_JSON_pack_string ("order_status_url",
689 : order_status_url),
690 : GNUNET_JSON_pack_string ("taler_pay_qrcode_svg",
691 : qr),
692 : GNUNET_JSON_pack_string ("order_summary",
693 : get_order_summary (god)));
694 0 : res = TALER_TEMPLATING_reply (god->sc.con,
695 : MHD_HTTP_PAYMENT_REQUIRED,
696 : "request_payment",
697 0 : god->hc->instance->settings.id,
698 : taler_pay_uri,
699 : context);
700 0 : if (GNUNET_SYSERR == res)
701 : {
702 0 : GNUNET_break (0);
703 0 : ret = MHD_NO;
704 : }
705 : else
706 : {
707 0 : ret = MHD_YES;
708 : }
709 0 : json_decref (context);
710 : }
711 0 : GNUNET_free (qr);
712 : }
713 : }
714 : else /* end of 'generate HTML' */
715 : {
716 0 : ret = TALER_MHD_REPLY_JSON_PACK (
717 : god->sc.con,
718 : MHD_HTTP_PAYMENT_REQUIRED,
719 : GNUNET_JSON_pack_string ("taler_pay_uri",
720 : taler_pay_uri),
721 : GNUNET_JSON_pack_allow_null (
722 : GNUNET_JSON_pack_string ("fulfillment_url",
723 : god->fulfillment_url)),
724 : GNUNET_JSON_pack_allow_null (
725 : GNUNET_JSON_pack_string ("already_paid_order_id",
726 : already_paid_order_id)));
727 : }
728 0 : GNUNET_free (taler_pay_uri);
729 0 : GNUNET_free (order_status_url);
730 0 : return ret;
731 : }
732 :
733 :
734 : /**
735 : * Function called with detailed information about a refund.
736 : * It is responsible for packing up the data to return.
737 : *
738 : * @param cls closure
739 : * @param refund_serial unique serial number of the refund
740 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
741 : * @param coin_pub public coin from which the refund comes from
742 : * @param exchange_url URL of the exchange that issued @a coin_pub
743 : * @param rtransaction_id identificator of the refund
744 : * @param reason human-readable explanation of the refund
745 : * @param refund_amount refund amount which is being taken from @a coin_pub
746 : * @param pending true if the this refund was not yet processed by the wallet/exchange
747 : */
748 : static void
749 0 : process_refunds_cb (void *cls,
750 : uint64_t refund_serial,
751 : struct GNUNET_TIME_Timestamp timestamp,
752 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
753 : const char *exchange_url,
754 : uint64_t rtransaction_id,
755 : const char *reason,
756 : const struct TALER_Amount *refund_amount,
757 : bool pending)
758 : {
759 0 : struct GetOrderData *god = cls;
760 :
761 : (void) refund_serial;
762 : (void) timestamp;
763 : (void) exchange_url;
764 : (void) rtransaction_id;
765 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
766 : "Found refund of %s for coin %s with reason `%s' in database\n",
767 : TALER_amount2s (refund_amount),
768 : TALER_B2S (coin_pub),
769 : reason);
770 0 : god->refund_pending |= pending;
771 0 : if (! pending)
772 : {
773 0 : GNUNET_assert (0 <=
774 : TALER_amount_add (&god->refund_taken,
775 : &god->refund_taken,
776 : refund_amount));
777 : }
778 0 : GNUNET_assert (0 <=
779 : TALER_amount_add (&god->refund_amount,
780 : &god->refund_amount,
781 : refund_amount));
782 0 : god->refunded = true;
783 0 : }
784 :
785 :
786 : /**
787 : * Clean up the session state for a GET /orders/$ID request.
788 : *
789 : * @param cls must be a `struct GetOrderData *`
790 : */
791 : static void
792 0 : god_cleanup (void *cls)
793 : {
794 0 : struct GetOrderData *god = cls;
795 :
796 0 : if (NULL != god->contract_terms)
797 : {
798 0 : json_decref (god->contract_terms);
799 0 : god->contract_terms = NULL;
800 : }
801 0 : if (NULL != god->refund_eh)
802 : {
803 0 : TMH_db->event_listen_cancel (god->refund_eh);
804 0 : god->refund_eh = NULL;
805 : }
806 0 : if (NULL != god->pay_eh)
807 : {
808 0 : TMH_db->event_listen_cancel (god->pay_eh);
809 0 : god->pay_eh = NULL;
810 : }
811 0 : GNUNET_free (god);
812 0 : }
813 :
814 :
815 : MHD_RESULT
816 0 : TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
817 : struct MHD_Connection *connection,
818 : struct TMH_HandlerContext *hc)
819 : {
820 0 : struct GetOrderData *god = hc->ctx;
821 0 : const char *order_id = hc->infix;
822 : enum GNUNET_DB_QueryStatus qs;
823 0 : bool contract_match = false;
824 0 : bool token_match = false;
825 0 : bool h_contract_provided = false;
826 0 : bool claim_token_provided = false;
827 0 : bool contract_available = false;
828 : const char *merchant_base_url;
829 :
830 : (void) rh;
831 0 : if (NULL == god)
832 : {
833 0 : god = GNUNET_new (struct GetOrderData);
834 0 : hc->ctx = god;
835 0 : hc->cc = &god_cleanup;
836 0 : god->sc.con = connection;
837 0 : god->hc = hc;
838 0 : god->order_id = order_id;
839 0 : god->generate_html = TMH_MHD_test_html_desired (connection);
840 :
841 :
842 : /* first-time initialization / sanity checks */
843 : {
844 : const char *cts;
845 :
846 0 : cts = MHD_lookup_connection_value (connection,
847 : MHD_GET_ARGUMENT_KIND,
848 : "h_contract");
849 0 : if ( (NULL != cts) &&
850 : (GNUNET_OK !=
851 0 : GNUNET_CRYPTO_hash_from_string (cts,
852 : &god->h_contract_terms.hash)) )
853 : {
854 : /* cts has wrong encoding */
855 0 : GNUNET_break_op (0);
856 0 : return TALER_MHD_reply_with_error (connection,
857 : MHD_HTTP_BAD_REQUEST,
858 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
859 : "h_contract");
860 : }
861 0 : if (NULL != cts)
862 0 : h_contract_provided = true;
863 : }
864 :
865 : {
866 : const char *ct;
867 :
868 0 : ct = MHD_lookup_connection_value (connection,
869 : MHD_GET_ARGUMENT_KIND,
870 : "token");
871 0 : if ( (NULL != ct) &&
872 : (GNUNET_OK !=
873 0 : GNUNET_STRINGS_string_to_data (ct,
874 : strlen (ct),
875 0 : &god->claim_token,
876 : sizeof (god->claim_token))) )
877 : {
878 : /* ct has wrong encoding */
879 0 : GNUNET_break_op (0);
880 0 : return TALER_MHD_reply_with_error (connection,
881 : MHD_HTTP_BAD_REQUEST,
882 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
883 : "token");
884 : }
885 0 : if (NULL != ct)
886 0 : claim_token_provided = true;
887 : }
888 0 : god->session_id = MHD_lookup_connection_value (connection,
889 : MHD_GET_ARGUMENT_KIND,
890 : "session_id");
891 :
892 : /* process await_refund_obtained argument */
893 : {
894 : const char *await_refund_obtained_s;
895 :
896 : await_refund_obtained_s =
897 0 : MHD_lookup_connection_value (connection,
898 : MHD_GET_ARGUMENT_KIND,
899 : "await_refund_obtained");
900 0 : god->sc.awaiting_refund_obtained =
901 : (NULL != await_refund_obtained_s)
902 0 : ? 0 == strcasecmp (await_refund_obtained_s,
903 : "yes")
904 0 : : false;
905 0 : if (god->sc.awaiting_refund_obtained)
906 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
907 : "Awaiting refund obtained\n");
908 : }
909 :
910 : {
911 : const char *min_refund;
912 :
913 0 : min_refund = MHD_lookup_connection_value (connection,
914 : MHD_GET_ARGUMENT_KIND,
915 : "refund");
916 0 : if (NULL != min_refund)
917 : {
918 0 : if ( (GNUNET_OK !=
919 0 : TALER_string_to_amount (min_refund,
920 0 : &god->sc.refund_expected)) ||
921 0 : (0 != strcasecmp (god->sc.refund_expected.currency,
922 : TMH_currency) ) )
923 : {
924 0 : GNUNET_break_op (0);
925 0 : return TALER_MHD_reply_with_error (connection,
926 : MHD_HTTP_BAD_REQUEST,
927 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
928 : "refund");
929 : }
930 0 : god->sc.awaiting_refund = true;
931 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
932 : "Awaiting minimum refund of %s\n",
933 : min_refund);
934 : }
935 : }
936 :
937 :
938 : /* process timeout_ms argument */
939 : {
940 : const char *long_poll_timeout_ms;
941 :
942 0 : long_poll_timeout_ms = MHD_lookup_connection_value (connection,
943 : MHD_GET_ARGUMENT_KIND,
944 : "timeout_ms");
945 0 : if (NULL != long_poll_timeout_ms)
946 : {
947 : unsigned int timeout_ms;
948 : char dummy;
949 :
950 0 : if (1 != sscanf (long_poll_timeout_ms,
951 : "%u%c",
952 : &timeout_ms,
953 : &dummy))
954 : {
955 0 : GNUNET_break_op (0);
956 0 : return TALER_MHD_reply_with_error (connection,
957 : MHD_HTTP_BAD_REQUEST,
958 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
959 : "timeout_ms (must be non-negative number)");
960 : }
961 : /* If HTML is requested, we never long poll. Makes no sense */
962 0 : if (! god->generate_html)
963 : {
964 : struct GNUNET_TIME_Relative timeout;
965 :
966 0 : timeout = GNUNET_TIME_relative_multiply (
967 : GNUNET_TIME_UNIT_MILLISECONDS,
968 : timeout_ms);
969 : god->sc.long_poll_timeout
970 0 : = GNUNET_TIME_relative_to_absolute (timeout);
971 0 : if (! GNUNET_TIME_relative_is_zero (timeout))
972 : {
973 0 : if (god->sc.awaiting_refund ||
974 0 : god->sc.awaiting_refund_obtained)
975 : {
976 0 : struct TMH_OrderPayEventP refund_eh = {
977 0 : .header.size = htons (sizeof (refund_eh)),
978 0 : .header.type = htons (god->sc.awaiting_refund_obtained
979 : ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
980 : : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
981 0 : .merchant_pub = hc->instance->merchant_pub
982 : };
983 :
984 0 : GNUNET_CRYPTO_hash (god->order_id,
985 : strlen (god->order_id),
986 : &refund_eh.h_order_id);
987 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
988 : "Subscribing %p to refunds on %s\n",
989 : god,
990 : god->order_id);
991 0 : god->refund_eh = TMH_db->event_listen (TMH_db->cls,
992 : &refund_eh.header,
993 : timeout,
994 : &resume_by_event,
995 : god);
996 : }
997 : {
998 0 : struct TMH_OrderPayEventP pay_eh = {
999 0 : .header.size = htons (sizeof (pay_eh)),
1000 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
1001 0 : .merchant_pub = hc->instance->merchant_pub
1002 : };
1003 :
1004 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1005 : "Subscribing to payments on %s\n",
1006 : god->order_id);
1007 0 : GNUNET_CRYPTO_hash (god->order_id,
1008 : strlen (god->order_id),
1009 : &pay_eh.h_order_id);
1010 0 : god->pay_eh = TMH_db->event_listen (TMH_db->cls,
1011 : &pay_eh.header,
1012 : timeout,
1013 : &resume_by_event,
1014 : god);
1015 : }
1016 : } /* end of timeout non-zero */
1017 : } /* end of HTML generation NOT requested */
1018 : } /* end of timeout_ms argument provided */
1019 : } /* end of timeout_ms argument handling */
1020 :
1021 : } /* end of first-time initialization / sanity checks */
1022 :
1023 0 : if (GNUNET_SYSERR == god->suspended)
1024 0 : return MHD_NO; /* we are in shutdown */
1025 0 : if (GNUNET_YES == god->suspended)
1026 : {
1027 0 : god->suspended = GNUNET_NO;
1028 0 : GNUNET_CONTAINER_DLL_remove (god_head,
1029 : god_tail,
1030 : god);
1031 : }
1032 :
1033 : /* Convert order_id to h_contract_terms */
1034 0 : TMH_db->preflight (TMH_db->cls);
1035 0 : if (NULL == god->contract_terms)
1036 : {
1037 : uint64_t order_serial;
1038 0 : bool paid = false;
1039 : struct TALER_ClaimTokenP db_claim_token;
1040 :
1041 0 : qs = TMH_db->lookup_contract_terms (TMH_db->cls,
1042 0 : hc->instance->settings.id,
1043 : order_id,
1044 : &god->contract_terms,
1045 : &order_serial,
1046 : &paid,
1047 : &db_claim_token);
1048 0 : if (0 > qs)
1049 : {
1050 : /* single, read-only SQL statements should never cause
1051 : serialization problems */
1052 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
1053 : /* Always report on hard error as well to enable diagnostics */
1054 0 : GNUNET_break (0);
1055 0 : return TALER_MHD_reply_with_error (connection,
1056 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1057 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1058 : "lookup_contract_terms");
1059 : }
1060 :
1061 : /* Note: when "!ord.requireClaimToken" and the client does not provide
1062 : a claim token (all zeros!), then token_match==TRUE below: */
1063 0 : token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
1064 0 : && (0 == GNUNET_memcmp (&db_claim_token,
1065 : &god->claim_token));
1066 : }
1067 :
1068 : /* Check if client provided the right hash code of the contract terms */
1069 0 : if (NULL != god->contract_terms)
1070 : {
1071 0 : contract_available = true;
1072 :
1073 0 : if (GNUNET_YES == GNUNET_is_zero (&god->h_contract_terms))
1074 : {
1075 :
1076 0 : if (GNUNET_OK !=
1077 0 : TALER_JSON_contract_hash (god->contract_terms,
1078 : &god->h_contract_terms))
1079 : {
1080 0 : GNUNET_break (0);
1081 0 : return TALER_MHD_reply_with_error (connection,
1082 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1083 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1084 : "contract terms");
1085 : }
1086 :
1087 : }
1088 : else
1089 : {
1090 :
1091 : struct TALER_PrivateContractHashP h;
1092 :
1093 0 : if (GNUNET_OK !=
1094 0 : TALER_JSON_contract_hash (god->contract_terms,
1095 : &h))
1096 : {
1097 0 : GNUNET_break (0);
1098 0 : return TALER_MHD_reply_with_error (connection,
1099 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1100 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1101 : "contract terms");
1102 : }
1103 0 : contract_match = (0 ==
1104 0 : GNUNET_memcmp (&h,
1105 : &god->h_contract_terms));
1106 0 : if (! contract_match)
1107 : {
1108 0 : GNUNET_break_op (0);
1109 0 : return TALER_MHD_reply_with_error (
1110 : connection,
1111 : MHD_HTTP_FORBIDDEN,
1112 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
1113 : NULL);
1114 : }
1115 :
1116 : }
1117 :
1118 : }
1119 :
1120 0 : if (contract_available)
1121 : {
1122 0 : god->claimed = true;
1123 : }
1124 : else
1125 : {
1126 : struct TALER_ClaimTokenP db_claim_token;
1127 : struct TALER_MerchantPostDataHashP unused;
1128 :
1129 0 : qs = TMH_db->lookup_order (TMH_db->cls,
1130 0 : hc->instance->settings.id,
1131 : order_id,
1132 : &db_claim_token,
1133 : &unused,
1134 0 : (NULL == god->contract_terms)
1135 : ? &god->contract_terms
1136 : : NULL);
1137 0 : if (0 > qs)
1138 : {
1139 : /* single, read-only SQL statements should never cause
1140 : serialization problems */
1141 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
1142 : /* Always report on hard error as well to enable diagnostics */
1143 0 : GNUNET_break (0);
1144 0 : return TALER_MHD_reply_with_error (connection,
1145 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1146 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1147 : "lookup_order");
1148 : }
1149 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1150 : {
1151 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1152 : "Unknown order id given: `%s'\n",
1153 : order_id);
1154 0 : return TALER_MHD_reply_with_error (connection,
1155 : MHD_HTTP_NOT_FOUND,
1156 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
1157 : order_id);
1158 : }
1159 : /* Note: when "!ord.requireClaimToken" and the client does not provide
1160 : a claim token (all zeros!), then token_match==TRUE below: */
1161 0 : token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
1162 0 : (0 == GNUNET_memcmp (&db_claim_token,
1163 : &god->claim_token));
1164 : } /* end unclaimed order logic */
1165 :
1166 0 : GNUNET_assert (NULL != god->contract_terms);
1167 0 : merchant_base_url = json_string_value (json_object_get (god->contract_terms,
1168 : "merchant_base_url"));
1169 0 : if (NULL == merchant_base_url)
1170 : {
1171 0 : GNUNET_break (0);
1172 0 : return TALER_MHD_reply_with_error (connection,
1173 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1174 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1175 : order_id);
1176 : }
1177 :
1178 0 : if (NULL == god->fulfillment_url)
1179 0 : god->fulfillment_url = json_string_value (json_object_get (
1180 0 : god->contract_terms,
1181 : "fulfillment_url"));
1182 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1183 : "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
1184 : token_match,
1185 : contract_available,
1186 : contract_match,
1187 : god->claimed);
1188 :
1189 0 : if (claim_token_provided && ! token_match)
1190 : {
1191 : /* Authentication provided but wrong. */
1192 0 : GNUNET_break_op (0);
1193 0 : return TALER_MHD_reply_with_error (connection,
1194 : MHD_HTTP_FORBIDDEN,
1195 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
1196 : "authentication with claim token provided but wrong");
1197 : }
1198 :
1199 0 : if (h_contract_provided && ! contract_match)
1200 : {
1201 : /* Authentication provided but wrong. */
1202 0 : GNUNET_break_op (0);
1203 0 : return TALER_MHD_reply_with_error (connection,
1204 : MHD_HTTP_FORBIDDEN,
1205 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
1206 : NULL);
1207 : }
1208 :
1209 0 : if (! (token_match ||
1210 : contract_match) )
1211 : {
1212 : const char *public_reorder_url;
1213 :
1214 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1215 : "Neither claim token nor contract matched\n");
1216 0 : public_reorder_url = json_string_value (json_object_get (
1217 0 : god->contract_terms,
1218 : "public_reorder_url"));
1219 : /* Client has no rights to this order */
1220 0 : if (NULL == public_reorder_url)
1221 : {
1222 : /* We cannot give the client a new order, just fail */
1223 0 : if (! GNUNET_is_zero (&god->h_contract_terms))
1224 : {
1225 0 : GNUNET_break_op (0);
1226 0 : return TALER_MHD_reply_with_error (
1227 : connection,
1228 : MHD_HTTP_FORBIDDEN,
1229 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
1230 : NULL);
1231 : }
1232 0 : return TALER_MHD_reply_with_error (connection,
1233 : MHD_HTTP_FORBIDDEN,
1234 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
1235 : "no 'public_reorder_url'");
1236 : }
1237 : /* We have a fulfillment URL, redirect the client there, maybe
1238 : the frontend can generate a fresh order for this new customer */
1239 0 : if (god->generate_html)
1240 : {
1241 : /* Contract was claimed (maybe by another device), so this client
1242 : cannot get the status information. Redirect to fulfillment page,
1243 : where the client may be able to pickup a fresh order -- or might
1244 : be able authenticate via session ID */
1245 : struct MHD_Response *reply;
1246 : MHD_RESULT ret;
1247 :
1248 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1249 : "Contract claimed, redirecting to fulfillment page for order %s\n",
1250 : order_id);
1251 0 : reply = MHD_create_response_from_buffer (0,
1252 : NULL,
1253 : MHD_RESPMEM_PERSISTENT);
1254 0 : if (NULL == reply)
1255 : {
1256 0 : GNUNET_break (0);
1257 0 : return MHD_NO;
1258 : }
1259 0 : GNUNET_break (MHD_YES ==
1260 : MHD_add_response_header (reply,
1261 : MHD_HTTP_HEADER_LOCATION,
1262 : public_reorder_url));
1263 0 : ret = MHD_queue_response (connection,
1264 : MHD_HTTP_FOUND,
1265 : reply);
1266 0 : MHD_destroy_response (reply);
1267 0 : return ret;
1268 : }
1269 : /* Need to generate JSON reply */
1270 0 : return TALER_MHD_REPLY_JSON_PACK (
1271 : connection,
1272 : MHD_HTTP_ACCEPTED,
1273 : GNUNET_JSON_pack_string ("public_reorder_url",
1274 : public_reorder_url));
1275 : }
1276 :
1277 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1278 : "Claim token or contract matched\n");
1279 :
1280 0 : if ( (NULL != god->session_id) &&
1281 0 : (NULL != god->fulfillment_url) )
1282 : {
1283 : /* Check if client paid for this fulfillment article
1284 : already within this session, but using a different
1285 : order ID. If so, redirect the client to the order
1286 : it already paid. Allows, for example, the case
1287 : where a mobile phone pays for a browser's session,
1288 : where the mobile phone has a different order
1289 : ID (because it purchased the article earlier)
1290 : than the one that the browser is waiting for. */
1291 0 : char *already_paid_order_id = NULL;
1292 :
1293 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1294 : "Running re-purchase detection for %s/%s\n",
1295 : god->session_id,
1296 : god->fulfillment_url);
1297 0 : qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
1298 0 : hc->instance->settings.id,
1299 : god->fulfillment_url,
1300 : god->session_id,
1301 : &already_paid_order_id);
1302 0 : if (qs < 0)
1303 : {
1304 : /* single, read-only SQL statements should never cause
1305 : serialization problems */
1306 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
1307 : /* Always report on hard error as well to enable diagnostics */
1308 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
1309 0 : return TALER_MHD_reply_with_error (connection,
1310 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1311 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1312 : "order by fulfillment");
1313 : }
1314 0 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
1315 0 : (0 != strcmp (order_id,
1316 : already_paid_order_id)) )
1317 : {
1318 : MHD_RESULT ret;
1319 :
1320 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1321 : "Sending pay request for order %s (already paid: %s)\n",
1322 : order_id,
1323 : already_paid_order_id);
1324 0 : ret = send_pay_request (god,
1325 : already_paid_order_id);
1326 0 : GNUNET_free (already_paid_order_id);
1327 0 : return ret;
1328 : }
1329 0 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1330 0 : GNUNET_free (already_paid_order_id);
1331 : }
1332 :
1333 0 : if (! god->claimed)
1334 : {
1335 : /* Order is unclaimed, no need to check for payments or even
1336 : refunds, simply always generate payment request */
1337 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1338 : "Order unclaimed, sending pay request for order %s\n",
1339 : order_id);
1340 0 : return send_pay_request (god,
1341 : NULL);
1342 : }
1343 :
1344 : {
1345 : /* Check if paid. */
1346 : struct TALER_PrivateContractHashP h_contract;
1347 : bool paid;
1348 :
1349 0 : qs = TMH_db->lookup_order_status (TMH_db->cls,
1350 0 : hc->instance->settings.id,
1351 : order_id,
1352 : &h_contract,
1353 : &paid);
1354 0 : if (0 >= qs)
1355 : {
1356 : /* Always report on hard error as well to enable diagnostics */
1357 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
1358 0 : return TALER_MHD_reply_with_error (connection,
1359 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1360 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1361 : "lookup_order_status");
1362 : }
1363 0 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1364 0 : if (! paid)
1365 : {
1366 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1367 : "Order claimed but unpaid, sending pay request for order %s\n",
1368 : order_id);
1369 0 : return send_pay_request (god,
1370 : NULL);
1371 : }
1372 : }
1373 :
1374 : /* At this point, we know the contract was paid. Let's check for
1375 : refunds. First, clear away refunds found from previous invocations. */
1376 0 : GNUNET_assert (GNUNET_OK ==
1377 : TALER_amount_set_zero (TMH_currency,
1378 : &god->refund_amount));
1379 0 : GNUNET_assert (GNUNET_OK ==
1380 : TALER_amount_set_zero (TMH_currency,
1381 : &god->refund_taken));
1382 0 : qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
1383 0 : hc->instance->settings.id,
1384 0 : &god->h_contract_terms,
1385 : &process_refunds_cb,
1386 : god);
1387 0 : if (0 > qs)
1388 : {
1389 0 : GNUNET_break (0);
1390 0 : return TALER_MHD_reply_with_error (connection,
1391 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1392 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1393 : "lookup_refunds_detailed");
1394 : }
1395 :
1396 0 : if ( ((god->sc.awaiting_refund) &&
1397 0 : ( (! god->refunded) ||
1398 0 : (1 != TALER_amount_cmp (&god->refund_amount,
1399 0 : &god->sc.refund_expected)) )) ||
1400 0 : ( (god->sc.awaiting_refund_obtained) &&
1401 0 : (god->refund_pending) ) )
1402 : {
1403 : /* Client is waiting for a refund larger than what we have, suspend
1404 : until timeout */
1405 : struct GNUNET_TIME_Relative remaining;
1406 :
1407 0 : remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
1408 0 : if (! GNUNET_TIME_relative_is_zero (remaining))
1409 : {
1410 : /* yes, indeed suspend */
1411 0 : if (god->sc.awaiting_refund)
1412 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1413 : "Awaiting refund exceeding %s\n",
1414 : TALER_amount2s (&god->sc.refund_expected));
1415 0 : if (god->sc.awaiting_refund_obtained)
1416 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1417 : "Awaiting pending refunds\n");
1418 0 : suspend_god (god);
1419 0 : return MHD_YES;
1420 : }
1421 : }
1422 :
1423 : /* All operations done, build final response */
1424 0 : if (god->generate_html)
1425 : {
1426 : enum GNUNET_GenericReturnValue res;
1427 :
1428 0 : if (god->refund_pending)
1429 : {
1430 : char *qr;
1431 : char *uri;
1432 :
1433 0 : GNUNET_assert (NULL != god->contract_terms);
1434 0 : uri = make_taler_refund_uri (merchant_base_url,
1435 : order_id);
1436 0 : if (NULL == uri)
1437 : {
1438 0 : GNUNET_break (0);
1439 0 : return TALER_MHD_reply_with_error (god->sc.con,
1440 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1441 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1442 : "refund URI");
1443 : }
1444 0 : qr = TMH_create_qrcode (uri);
1445 0 : if (NULL == qr)
1446 : {
1447 0 : GNUNET_break (0);
1448 0 : GNUNET_free (uri);
1449 0 : return TALER_MHD_reply_with_error (god->sc.con,
1450 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1451 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1452 : "qr code");
1453 : }
1454 : {
1455 : json_t *context;
1456 :
1457 0 : context = GNUNET_JSON_PACK (
1458 : GNUNET_JSON_pack_string ("order_summary",
1459 : get_order_summary (god)),
1460 : TALER_JSON_pack_amount ("refund_amount",
1461 : &god->refund_amount),
1462 : TALER_JSON_pack_amount ("refund_taken",
1463 : &god->refund_taken),
1464 : GNUNET_JSON_pack_string ("taler_refund_uri",
1465 : uri),
1466 : GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
1467 : qr));
1468 0 : res = TALER_TEMPLATING_reply (god->sc.con,
1469 : MHD_HTTP_OK,
1470 : "offer_refund",
1471 0 : hc->instance->settings.id,
1472 : uri,
1473 : context);
1474 0 : json_decref (context);
1475 : }
1476 0 : GNUNET_free (uri);
1477 0 : GNUNET_free (qr);
1478 : }
1479 : else
1480 : {
1481 : json_t *context;
1482 :
1483 0 : context = GNUNET_JSON_PACK (
1484 : GNUNET_JSON_pack_object_incref ("contract_terms",
1485 : god->contract_terms),
1486 : GNUNET_JSON_pack_string ("order_summary",
1487 : get_order_summary (god)),
1488 : TALER_JSON_pack_amount ("refund_amount",
1489 : &god->refund_amount),
1490 : TALER_JSON_pack_amount ("refund_taken",
1491 : &god->refund_taken));
1492 0 : res = TALER_TEMPLATING_reply (god->sc.con,
1493 : MHD_HTTP_OK,
1494 : "show_order_details",
1495 0 : hc->instance->settings.id,
1496 : NULL,
1497 : context);
1498 0 : json_decref (context);
1499 : }
1500 0 : if (GNUNET_SYSERR == res)
1501 : {
1502 0 : GNUNET_break (0);
1503 0 : return MHD_NO;
1504 : }
1505 0 : return MHD_YES;
1506 : }
1507 0 : return TALER_MHD_REPLY_JSON_PACK (
1508 : connection,
1509 : MHD_HTTP_OK,
1510 : GNUNET_JSON_pack_allow_null (
1511 : GNUNET_JSON_pack_string ("fulfillment_url",
1512 : god->fulfillment_url)),
1513 : GNUNET_JSON_pack_bool ("refunded",
1514 : god->refunded),
1515 : GNUNET_JSON_pack_bool ("refund_pending",
1516 : god->refund_pending),
1517 : TALER_JSON_pack_amount ("refund_taken",
1518 : &god->refund_taken),
1519 : TALER_JSON_pack_amount ("refund_amount",
1520 : &god->refund_amount));
1521 : }
1522 :
1523 :
1524 : /* end of taler-merchant-httpd_get-orders-ID.c */
|