Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2026 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 src/backend/taler-merchant-httpd_get-orders-ORDER_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 <gnunet/gnunet_common.h>
26 : #include <taler/taler_signatures.h>
27 : #include <taler/taler_dbevents.h>
28 : #include <taler/taler_json_lib.h>
29 : #include <taler/taler_templating_lib.h>
30 : #include <taler/taler_exchange_service.h>
31 : #include "taler-merchant-httpd_helper.h"
32 : #include "taler-merchant-httpd_get-orders-ORDER_ID.h"
33 : #include "taler-merchant-httpd_mhd.h"
34 : #include "taler-merchant-httpd_qr.h"
35 : #include "taler/taler_error_codes.h"
36 : #include "taler/taler_util.h"
37 : #include "taler/taler_merchant_util.h"
38 : #include "merchant-database/get_contract_terms_status.h"
39 : #include "merchant-database/get_order.h"
40 : #include "merchant-database/get_order_by_fulfillment.h"
41 : #include "merchant-database/get_order_status.h"
42 : #include "merchant-database/iterate_refunds_detailed.h"
43 : #include "merchant-database/event_listen.h"
44 : #include "merchant-database/preflight.h"
45 :
46 : /**
47 : * How often do we retry DB transactions on serialization failures?
48 : */
49 : #define MAX_RETRIES 5
50 :
51 :
52 : /**
53 : * The different phases in which we handle the request.
54 : */
55 : enum Phase
56 : {
57 : GOP_INIT = 0,
58 : GOP_LOOKUP_TERMS = 1,
59 : GOP_PARSE_CONTRACT = 2,
60 : GOP_CHECK_CLIENT_ACCESS = 3,
61 : GOP_CHECK_PAID = 4,
62 : GOP_REDIRECT_TO_PAID_ORDER = 5,
63 : GOP_HANDLE_UNPAID = 6,
64 : GOP_CHECK_REFUNDED = 7,
65 : GOP_RETURN_STATUS = 8,
66 : GOP_RETURN_MHD_YES = 9,
67 : GOP_RETURN_MHD_NO = 10
68 : };
69 :
70 :
71 : /**
72 : * Context for the operation.
73 : */
74 : struct GetOrderData
75 : {
76 :
77 : /**
78 : * Hashed version of contract terms. All zeros if not provided.
79 : */
80 : struct TALER_PrivateContractHashP h_contract_terms;
81 :
82 : /**
83 : * Claim token used for access control. All zeros if not provided.
84 : */
85 : struct TALER_ClaimTokenP claim_token;
86 :
87 : /**
88 : * DLL of (suspended) requests.
89 : */
90 : struct GetOrderData *next;
91 :
92 : /**
93 : * DLL of (suspended) requests.
94 : */
95 : struct GetOrderData *prev;
96 :
97 : /**
98 : * Context of the request.
99 : */
100 : struct TMH_HandlerContext *hc;
101 :
102 : /**
103 : * Entry in the #resume_timeout_heap for this check payment, if we are
104 : * suspended.
105 : */
106 : struct TMH_SuspendedConnection sc;
107 :
108 : /**
109 : * Database event we are waiting on to be resuming on payment.
110 : */
111 : struct GNUNET_DB_EventHandler *pay_eh;
112 :
113 : /**
114 : * Database event we are waiting on to be resuming for refunds.
115 : */
116 : struct GNUNET_DB_EventHandler *refund_eh;
117 :
118 : /**
119 : * Database event we are waiting on to be resuming for repurchase
120 : * detection updating some equivalent order (same fulfillment URL)
121 : * to our session.
122 : */
123 : struct GNUNET_DB_EventHandler *session_eh;
124 :
125 : /**
126 : * Which merchant instance is this for?
127 : */
128 : struct MerchantInstance *mi;
129 :
130 : /**
131 : * order ID for the payment
132 : */
133 : const char *order_id;
134 :
135 : /**
136 : * session of the client
137 : */
138 : const char *session_id;
139 :
140 : /**
141 : * choice index (contract v1)
142 : */
143 : int16_t choice_index;
144 :
145 : /**
146 : * Contract terms of the payment we are checking. NULL when they
147 : * are not (yet) known.
148 : */
149 : json_t *contract_terms_json;
150 :
151 : /**
152 : * Parsed contract terms, NULL when parsing failed.
153 : */
154 : struct TALER_MERCHANT_Contract *contract_terms;
155 :
156 : /**
157 : * Parsed proto contract terms, NULL when parsing failed.
158 : * Careful, aliased with pc in @e contract_terms if @e contract_terms is
159 : * not NULL!
160 : */
161 : struct TALER_MERCHANT_ProtoContract *pc;
162 :
163 : /**
164 : * Order of the payment we are checking. NULL when we have a contract.
165 : */
166 : json_t *order_json;
167 :
168 : /**
169 : * Parsed order, NULL when parsing failed.
170 : */
171 : struct TALER_MERCHANT_Order *order;
172 :
173 : /**
174 : * Common terms from @e order or @e contract
175 : */
176 : const struct TALER_MERCHANT_ContractBaseTerms *ct;
177 :
178 : /**
179 : * Total refunds granted for this payment. Only initialized
180 : * if @e refunded is set to true.
181 : */
182 : struct TALER_Amount refund_amount;
183 :
184 : /**
185 : * Total refunds already collected.
186 : * if @e refunded is set to true.
187 : */
188 : struct TALER_Amount refund_taken;
189 :
190 : /**
191 : * Phase in which we currently are handling this
192 : * request.
193 : */
194 : enum Phase phase;
195 :
196 : /**
197 : * Return code: #TALER_EC_NONE if successful.
198 : */
199 : enum TALER_ErrorCode ec;
200 :
201 : /**
202 : * Did we suspend @a connection and are thus in
203 : * the #god_head DLL (#GNUNET_YES). Set to
204 : * #GNUNET_NO if we are not suspended, and to
205 : * #GNUNET_SYSERR if we should close the connection
206 : * without a response due to shutdown.
207 : */
208 : enum GNUNET_GenericReturnValue suspended;
209 :
210 : /**
211 : * Set to YES if refunded orders should be included when
212 : * doing repurchase detection.
213 : */
214 : enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
215 :
216 : /**
217 : * Set to true if the client passed 'h_contract'.
218 : */
219 : bool h_contract_provided;
220 :
221 : /**
222 : * Set to true if the client passed a 'claim' token.
223 : */
224 : bool claim_token_provided;
225 :
226 : /**
227 : * Set to true if we are dealing with a claimed order
228 : * (and thus @e h_contract_terms is set, otherwise certain
229 : * DB queries will not work).
230 : */
231 : bool claimed;
232 :
233 : /**
234 : * Set to true if this order was paid.
235 : */
236 : bool paid;
237 :
238 : /**
239 : * Set to true if this order has been refunded and
240 : * @e refund_amount is initialized.
241 : */
242 : bool refunded;
243 :
244 : /**
245 : * Set to true if a refund is still available for the
246 : * wallet for this payment.
247 : * @deprecated: true if refund_taken < refund_amount
248 : */
249 : bool refund_pending;
250 :
251 : /**
252 : * Set to true if the client requested HTML, otherwise we generate JSON.
253 : */
254 : bool generate_html;
255 :
256 : /**
257 : * Did we parse the order?
258 : */
259 : bool order_parsed;
260 :
261 : /**
262 : * Set to true if the refunds found in the DB have
263 : * a different currency then the main contract.
264 : */
265 : bool bad_refund_currency_in_db;
266 :
267 : /**
268 : * Did the hash of the contract match the contract
269 : * hash supplied by the client?
270 : */
271 : bool contract_match;
272 :
273 : /**
274 : * True if we had a claim token and the claim token
275 : * provided by the client matched our claim token.
276 : */
277 : bool token_match;
278 :
279 : /**
280 : * True if we found a (claimed) contract for the order,
281 : * false if we had an unclaimed order.
282 : */
283 : bool contract_available;
284 :
285 : };
286 :
287 :
288 : /**
289 : * Head of DLL of (suspended) requests.
290 : */
291 : static struct GetOrderData *god_head;
292 :
293 : /**
294 : * Tail of DLL of (suspended) requests.
295 : */
296 : static struct GetOrderData *god_tail;
297 :
298 :
299 : void
300 20 : TMH_force_wallet_get_order_resume (void)
301 : {
302 : struct GetOrderData *god;
303 :
304 20 : while (NULL != (god = god_head))
305 : {
306 0 : GNUNET_CONTAINER_DLL_remove (god_head,
307 : god_tail,
308 : god);
309 0 : GNUNET_assert (god->suspended);
310 0 : god->suspended = GNUNET_SYSERR;
311 0 : MHD_resume_connection (god->sc.con);
312 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
313 : }
314 20 : }
315 :
316 :
317 : /**
318 : * Suspend this @a god until the trigger is satisfied.
319 : *
320 : * @param god request to suspend
321 : */
322 : static void
323 9 : suspend_god (struct GetOrderData *god)
324 : {
325 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
326 : "Suspending GET /orders/%s\n",
327 : god->order_id);
328 : /* We reset the contract terms and start by looking them up
329 : again, as while we are suspended fundamental things could
330 : change (such as the contract being claimed) */
331 9 : if (NULL != god->contract_terms_json)
332 : {
333 9 : json_decref (god->contract_terms_json);
334 9 : god->contract_terms_json = NULL;
335 : }
336 9 : if (NULL != god->order_json)
337 : {
338 0 : json_decref (god->order_json);
339 0 : god->order_json = NULL;
340 0 : god->order_parsed = false;
341 : }
342 9 : if (NULL != god->contract_terms)
343 : {
344 9 : TALER_MERCHANT_contract_free (god->contract_terms);
345 9 : god->contract_terms = NULL;
346 9 : god->pc = NULL;
347 : }
348 9 : if (NULL != god->pc)
349 : {
350 0 : TALER_MERCHANT_proto_contract_free (god->pc);
351 0 : god->pc = NULL;
352 : }
353 9 : if (NULL != god->order)
354 : {
355 0 : TALER_MERCHANT_order_free (god->order);
356 0 : god->order = NULL;
357 : }
358 9 : god->ct = NULL; /* ensure not dangling */
359 9 : GNUNET_assert (! god->suspended);
360 9 : god->contract_match = false;
361 9 : god->token_match = false;
362 9 : god->contract_available = false;
363 9 : god->phase = GOP_LOOKUP_TERMS;
364 9 : god->suspended = GNUNET_YES;
365 9 : GNUNET_CONTAINER_DLL_insert (god_head,
366 : god_tail,
367 : god);
368 9 : MHD_suspend_connection (god->sc.con);
369 9 : }
370 :
371 :
372 : /**
373 : * Clean up the session state for a GET /orders/$ID request.
374 : *
375 : * @param cls must be a `struct GetOrderData *`
376 : */
377 : static void
378 40 : god_cleanup (void *cls)
379 : {
380 40 : struct GetOrderData *god = cls;
381 :
382 40 : if (NULL != god->contract_terms_json)
383 : {
384 33 : json_decref (god->contract_terms_json);
385 33 : god->contract_terms_json = NULL;
386 : }
387 40 : if (NULL != god->order_json)
388 : {
389 7 : json_decref (god->order_json);
390 7 : god->order_json = NULL;
391 : }
392 40 : if (NULL != god->contract_terms)
393 : {
394 33 : TALER_MERCHANT_contract_free (god->contract_terms);
395 33 : god->contract_terms = NULL;
396 33 : god->pc = NULL;
397 : }
398 40 : if (NULL != god->pc)
399 : {
400 0 : TALER_MERCHANT_proto_contract_free (god->pc);
401 0 : god->pc = NULL;
402 : }
403 40 : if (NULL != god->order)
404 : {
405 7 : TALER_MERCHANT_order_free (god->order);
406 7 : god->order = NULL;
407 : }
408 40 : god->ct = NULL; /* ensure not dangling */
409 40 : if (NULL != god->session_eh)
410 : {
411 8 : TALER_MERCHANTDB_event_listen_cancel (god->session_eh);
412 8 : god->session_eh = NULL;
413 : }
414 40 : if (NULL != god->refund_eh)
415 : {
416 5 : TALER_MERCHANTDB_event_listen_cancel (god->refund_eh);
417 5 : god->refund_eh = NULL;
418 : }
419 40 : if (NULL != god->pay_eh)
420 : {
421 9 : TALER_MERCHANTDB_event_listen_cancel (god->pay_eh);
422 9 : god->pay_eh = NULL;
423 : }
424 40 : GNUNET_free (god);
425 40 : }
426 :
427 :
428 : /**
429 : * Finish the request by returning @a mret as the
430 : * final result.
431 : *
432 : * @param[in,out] god request we are processing
433 : * @param mret MHD result to return
434 : */
435 : static void
436 40 : phase_end (struct GetOrderData *god,
437 : enum MHD_Result mret)
438 : {
439 40 : god->phase = (MHD_YES == mret)
440 : ? GOP_RETURN_MHD_YES
441 40 : : GOP_RETURN_MHD_NO;
442 40 : }
443 :
444 :
445 : /**
446 : * Finish the request by returning an error @a ec
447 : * with HTTP status @a http_status and @a message.
448 : *
449 : * @param[in,out] god request we are processing
450 : * @param http_status HTTP status code to return
451 : * @param ec error code to return
452 : * @param message human readable hint to return, can be NULL
453 : */
454 : static void
455 0 : phase_fail (struct GetOrderData *god,
456 : unsigned int http_status,
457 : enum TALER_ErrorCode ec,
458 : const char *message)
459 : {
460 0 : phase_end (god,
461 : TALER_MHD_reply_with_error (god->sc.con,
462 : http_status,
463 : ec,
464 : message));
465 0 : }
466 :
467 :
468 : /**
469 : * We have received a trigger from the database
470 : * that we should (possibly) resume the request.
471 : *
472 : * @param cls a `struct GetOrderData` to resume
473 : * @param extra string encoding refund amount (or NULL)
474 : * @param extra_size number of bytes in @a extra
475 : */
476 : static void
477 22 : resume_by_event (void *cls,
478 : const void *extra,
479 : size_t extra_size)
480 : {
481 22 : struct GetOrderData *god = cls;
482 : struct GNUNET_AsyncScopeSave old;
483 :
484 22 : GNUNET_async_scope_enter (&god->hc->async_scope_id,
485 : &old);
486 22 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
487 : "Received event for %s with argument `%.*s`\n",
488 : god->order_id,
489 : (int) extra_size,
490 : (const char *) extra);
491 22 : if (! god->suspended)
492 : {
493 5 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
494 : "Not suspended, ignoring event\n");
495 5 : GNUNET_async_scope_restore (&old);
496 13 : return; /* duplicate event is possible */
497 : }
498 17 : if (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) &&
499 17 : god->sc.awaiting_refund)
500 : {
501 : char *as;
502 : struct TALER_Amount a;
503 :
504 13 : if (0 == extra_size)
505 : {
506 6 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
507 : "No amount given, but need refund above threshold\n");
508 6 : GNUNET_async_scope_restore (&old);
509 8 : return; /* not relevant */
510 : }
511 7 : as = GNUNET_strndup (extra,
512 : extra_size);
513 7 : if (GNUNET_OK !=
514 7 : TALER_string_to_amount (as,
515 : &a))
516 : {
517 0 : GNUNET_break (0);
518 0 : GNUNET_async_scope_restore (&old);
519 0 : GNUNET_free (as);
520 0 : return;
521 : }
522 7 : GNUNET_free (as);
523 7 : if (GNUNET_OK !=
524 7 : TALER_amount_cmp_currency (&god->sc.refund_expected,
525 : &a))
526 : {
527 0 : GNUNET_break (0);
528 0 : GNUNET_async_scope_restore (&old);
529 0 : return; /* bad currency!? */
530 : }
531 7 : if (1 == TALER_amount_cmp (&god->sc.refund_expected,
532 : &a))
533 : {
534 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
535 : "Amount too small to trigger resuming\n");
536 2 : GNUNET_async_scope_restore (&old);
537 2 : return; /* refund too small */
538 : }
539 : }
540 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
541 : "Resuming (%s/%s) by event with argument `%.*s`\n",
542 : GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout)
543 : ? "future"
544 : : "past",
545 : god->sc.awaiting_refund
546 : ? "awaiting refund"
547 : : "not waiting for refund",
548 : (int) extra_size,
549 : (const char *) extra);
550 : /* Return the current status after a relevant event. In particular, a
551 : claim changes an order from unclaimed to claimed without changing its
552 : unpaid HTTP response, so re-entering the long poll here would hide the
553 : state transition until the original timeout. */
554 9 : god->sc.long_poll_timeout = GNUNET_TIME_absolute_get ();
555 9 : god->suspended = GNUNET_NO;
556 9 : GNUNET_CONTAINER_DLL_remove (god_head,
557 : god_tail,
558 : god);
559 9 : MHD_resume_connection (god->sc.con);
560 9 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
561 9 : GNUNET_async_scope_restore (&old);
562 : }
563 :
564 :
565 : /**
566 : * First phase (after request parsing).
567 : * Set up long-polling.
568 : *
569 : * @param[in,out] god request context
570 : */
571 : static void
572 40 : phase_init (struct GetOrderData *god)
573 : {
574 40 : god->phase++;
575 40 : if (god->generate_html)
576 5 : return; /* If HTML is requested, we never actually long poll. */
577 35 : if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout))
578 26 : return; /* long polling not requested */
579 :
580 9 : if (god->sc.awaiting_refund ||
581 4 : god->sc.awaiting_refund_obtained)
582 : {
583 10 : struct TMH_OrderPayEventP refund_eh = {
584 5 : .header.size = htons (sizeof (refund_eh)),
585 5 : .header.type = htons (god->sc.awaiting_refund_obtained
586 : ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
587 : : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
588 5 : .merchant_pub = god->hc->instance->merchant_pub
589 : };
590 :
591 5 : GNUNET_CRYPTO_hash (god->order_id,
592 : strlen (god->order_id),
593 : &refund_eh.h_order_id);
594 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
595 : "Subscribing %p to refunds on %s\n",
596 : god,
597 : god->order_id);
598 : god->refund_eh
599 5 : = TALER_MERCHANTDB_event_listen (
600 : TMH_db,
601 : &refund_eh.header,
602 : GNUNET_TIME_absolute_get_remaining (
603 : god->sc.long_poll_timeout),
604 : &resume_by_event,
605 : god);
606 : }
607 : {
608 9 : struct TMH_OrderPayEventP pay_eh = {
609 9 : .header.size = htons (sizeof (pay_eh)),
610 9 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED),
611 9 : .merchant_pub = god->hc->instance->merchant_pub
612 : };
613 :
614 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
615 : "Subscribing to order status changes on %s\n",
616 : god->order_id);
617 9 : GNUNET_CRYPTO_hash (god->order_id,
618 : strlen (god->order_id),
619 : &pay_eh.h_order_id);
620 : god->pay_eh
621 9 : = TALER_MERCHANTDB_event_listen (
622 : TMH_db,
623 : &pay_eh.header,
624 : GNUNET_TIME_absolute_get_remaining (
625 : god->sc.long_poll_timeout),
626 : &resume_by_event,
627 : god);
628 : }
629 : }
630 :
631 :
632 : /**
633 : * Lookup contract terms and check client has the
634 : * right to access this order (by claim token or
635 : * contract hash).
636 : *
637 : * @param[in,out] god request context
638 : */
639 : static void
640 49 : phase_lookup_terms (struct GetOrderData *god)
641 : {
642 : uint64_t order_serial;
643 : struct TALER_ClaimTokenP db_claim_token;
644 :
645 : /* Convert order_id to h_contract_terms */
646 49 : TALER_MERCHANTDB_preflight (TMH_db);
647 49 : GNUNET_assert (NULL == god->contract_terms_json);
648 49 : GNUNET_assert (NULL == god->order_json);
649 :
650 : {
651 : enum GNUNET_DB_QueryStatus qs;
652 :
653 : bool paid;
654 : bool wired;
655 : bool session_matches;
656 49 : qs = TALER_MERCHANTDB_get_contract_terms_status (
657 : TMH_db,
658 49 : god->hc->instance->settings.id,
659 : god->order_id,
660 : NULL,
661 : &god->contract_terms_json,
662 : &order_serial,
663 : &paid,
664 : &wired,
665 : &session_matches,
666 : &db_claim_token,
667 : &god->choice_index);
668 49 : if (0 > qs)
669 : {
670 : /* single, read-only SQL statements should never cause
671 : serialization problems */
672 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
673 : /* Always report on hard error as well to enable diagnostics */
674 0 : GNUNET_break (0);
675 0 : phase_fail (god,
676 : MHD_HTTP_INTERNAL_SERVER_ERROR,
677 : TALER_EC_GENERIC_DB_FETCH_FAILED,
678 : "get_contract_terms");
679 0 : return;
680 : }
681 : /* Note: when "!ord.requireClaimToken" and the client does not provide
682 : a claim token (all zeros!), then token_match==TRUE below: */
683 : god->token_match
684 49 : = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
685 49 : && (0 == GNUNET_memcmp (&db_claim_token,
686 : &god->claim_token));
687 : }
688 :
689 : /* Check if client provided the right hash code of the contract terms */
690 49 : if (NULL != god->contract_terms_json)
691 : {
692 42 : god->contract_available = true;
693 42 : if (GNUNET_YES ==
694 42 : GNUNET_is_zero (&god->h_contract_terms))
695 : {
696 3 : if (GNUNET_OK !=
697 3 : TALER_JSON_contract_hash (god->contract_terms_json,
698 : &god->h_contract_terms))
699 : {
700 0 : GNUNET_break (0);
701 0 : phase_fail (god,
702 : MHD_HTTP_INTERNAL_SERVER_ERROR,
703 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
704 : "contract terms");
705 0 : return;
706 : }
707 : }
708 : else
709 : {
710 : struct TALER_PrivateContractHashP h;
711 :
712 39 : if (GNUNET_OK !=
713 39 : TALER_JSON_contract_hash (god->contract_terms_json,
714 : &h))
715 : {
716 0 : GNUNET_break (0);
717 0 : phase_fail (god,
718 : MHD_HTTP_INTERNAL_SERVER_ERROR,
719 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
720 : "contract terms");
721 0 : return;
722 : }
723 39 : god->contract_match = (0 ==
724 39 : GNUNET_memcmp (&h,
725 : &god->h_contract_terms));
726 39 : if (! god->contract_match)
727 : {
728 0 : GNUNET_break_op (0);
729 0 : phase_fail (god,
730 : MHD_HTTP_FORBIDDEN,
731 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
732 : NULL);
733 0 : return;
734 : }
735 : }
736 : }
737 :
738 49 : if (god->contract_available)
739 : {
740 42 : god->claimed = true;
741 : }
742 : else
743 : {
744 : struct TALER_MerchantPostDataHashP unused;
745 : enum GNUNET_DB_QueryStatus qs;
746 :
747 : /* No contract exists yet, so no payment choice has been selected. */
748 7 : god->choice_index = -1;
749 :
750 7 : qs = TALER_MERCHANTDB_get_order (
751 : TMH_db,
752 7 : god->hc->instance->settings.id,
753 : god->order_id,
754 : &db_claim_token,
755 : &unused,
756 : &god->order_json);
757 7 : if (0 > qs)
758 : {
759 : /* single, read-only SQL statements should never cause
760 : serialization problems */
761 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
762 : /* Always report on hard error as well to enable diagnostics */
763 0 : GNUNET_break (0);
764 0 : phase_fail (god,
765 : MHD_HTTP_INTERNAL_SERVER_ERROR,
766 : TALER_EC_GENERIC_DB_FETCH_FAILED,
767 : "get_order");
768 0 : return;
769 : }
770 7 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
771 : {
772 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
773 : "Unknown order id given: `%s'\n",
774 : god->order_id);
775 0 : phase_fail (god,
776 : MHD_HTTP_NOT_FOUND,
777 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
778 : god->order_id);
779 0 : return;
780 : }
781 : /* Note: when "!ord.requireClaimToken" and the client does not provide
782 : a claim token (all zeros!), then token_match==TRUE below: */
783 : god->token_match
784 14 : = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
785 14 : (0 == GNUNET_memcmp (&db_claim_token,
786 : &god->claim_token));
787 : } /* end unclaimed order logic */
788 49 : god->phase++;
789 : }
790 :
791 :
792 : /**
793 : * Parse contract terms.
794 : *
795 : * @param[in,out] god request context
796 : */
797 : static void
798 49 : phase_parse_contract (struct GetOrderData *god)
799 : {
800 49 : GNUNET_break (NULL == god->contract_terms);
801 49 : GNUNET_break (NULL == god->order);
802 49 : if (NULL != god->contract_terms_json)
803 : {
804 42 : if (NULL ==
805 42 : json_object_get (god->contract_terms_json,
806 : "nonce"))
807 : {
808 0 : god->pc = TALER_MERCHANT_proto_contract_parse (
809 : god->contract_terms_json);
810 0 : if (NULL == god->pc)
811 : {
812 0 : phase_fail (god,
813 : MHD_HTTP_INTERNAL_SERVER_ERROR,
814 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
815 : god->order_id);
816 0 : return;
817 : }
818 0 : god->ct = god->pc->base;
819 : }
820 : else
821 : {
822 42 : god->contract_terms = TALER_MERCHANT_contract_parse (
823 : god->contract_terms_json);
824 42 : if (NULL == god->contract_terms)
825 : {
826 0 : phase_fail (god,
827 : MHD_HTTP_INTERNAL_SERVER_ERROR,
828 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
829 : god->order_id);
830 0 : return;
831 : }
832 42 : god->pc = god->contract_terms->pc;
833 42 : god->ct = god->contract_terms->pc->base;
834 : }
835 : }
836 49 : if (NULL != god->order_json)
837 : {
838 7 : god->order = TALER_MERCHANT_order_parse (
839 : god->order_json);
840 7 : if (NULL == god->order)
841 : {
842 0 : phase_fail (god,
843 : MHD_HTTP_INTERNAL_SERVER_ERROR,
844 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
845 : god->order_id);
846 0 : return;
847 : }
848 7 : god->order_parsed = true;
849 7 : god->ct = god->order->base;
850 : }
851 49 : GNUNET_assert ( (NULL != god->order) ||
852 : (NULL != god->pc) );
853 :
854 49 : if ( (NULL != god->session_id) &&
855 10 : (NULL != god->ct->fulfillment_url) &&
856 10 : (NULL == god->session_eh) )
857 : {
858 8 : struct TMH_SessionEventP session_eh = {
859 8 : .header.size = htons (sizeof (session_eh)),
860 8 : .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
861 8 : .merchant_pub = god->hc->instance->merchant_pub
862 : };
863 :
864 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
865 : "Subscribing to session triggers for %p\n",
866 : god);
867 8 : GNUNET_CRYPTO_hash (god->session_id,
868 : strlen (god->session_id),
869 : &session_eh.h_session_id);
870 8 : GNUNET_CRYPTO_hash (god->ct->fulfillment_url,
871 8 : strlen (god->ct->fulfillment_url),
872 : &session_eh.h_fulfillment_url);
873 : god->session_eh
874 8 : = TALER_MERCHANTDB_event_listen (
875 : TMH_db,
876 : &session_eh.header,
877 : GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout),
878 : &resume_by_event,
879 : god);
880 : }
881 49 : god->phase++;
882 : }
883 :
884 :
885 : /**
886 : * Check that this order is unclaimed or claimed by
887 : * this client.
888 : *
889 : * @param[in,out] god request context
890 : */
891 : static void
892 49 : phase_check_client_access (struct GetOrderData *god)
893 : {
894 49 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
895 : "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
896 : god->token_match,
897 : god->contract_available,
898 : god->contract_match,
899 : god->claimed);
900 :
901 49 : if (god->claim_token_provided && ! god->token_match)
902 : {
903 : /* Authentication provided but wrong. */
904 0 : GNUNET_break_op (0);
905 0 : phase_fail (god,
906 : MHD_HTTP_FORBIDDEN,
907 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
908 : "authentication with claim token provided but wrong");
909 0 : return;
910 : }
911 :
912 49 : if (god->h_contract_provided && ! god->contract_match)
913 : {
914 : /* Authentication provided but wrong. */
915 0 : GNUNET_break_op (0);
916 0 : phase_fail (god,
917 : MHD_HTTP_FORBIDDEN,
918 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
919 : NULL);
920 0 : return;
921 : }
922 :
923 49 : if (! (god->token_match ||
924 39 : god->contract_match) )
925 : {
926 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
927 : "Neither claim token nor contract matched\n");
928 : /* Client has no rights to this order */
929 0 : if (NULL == god->ct->public_reorder_url)
930 : {
931 : /* We cannot give the client a new order, just fail */
932 0 : if (! GNUNET_is_zero (&god->h_contract_terms))
933 : {
934 0 : GNUNET_break_op (0);
935 0 : phase_fail (god,
936 : MHD_HTTP_FORBIDDEN,
937 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
938 : NULL);
939 0 : return;
940 : }
941 0 : GNUNET_break_op (0);
942 0 : phase_fail (god,
943 : MHD_HTTP_FORBIDDEN,
944 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
945 : "no 'public_reorder_url'");
946 0 : return;
947 : }
948 : /* We have a fulfillment URL, redirect the client there, maybe
949 : the frontend can generate a fresh order for this new customer */
950 0 : if (god->generate_html)
951 : {
952 : /* Contract was claimed (maybe by another device), so this client
953 : cannot get the status information. Redirect to fulfillment page,
954 : where the client may be able to pickup a fresh order -- or might
955 : be able authenticate via session ID */
956 : struct MHD_Response *reply;
957 : enum MHD_Result ret;
958 :
959 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
960 : "Contract claimed, redirecting to fulfillment page for order %s\n",
961 : god->order_id);
962 0 : reply = MHD_create_response_from_buffer (0,
963 : NULL,
964 : MHD_RESPMEM_PERSISTENT);
965 0 : if (NULL == reply)
966 : {
967 0 : GNUNET_break (0);
968 0 : phase_end (god,
969 : MHD_NO);
970 0 : return;
971 : }
972 0 : GNUNET_break (MHD_YES ==
973 : MHD_add_response_header (
974 : reply,
975 : MHD_HTTP_HEADER_LOCATION,
976 : god->ct->public_reorder_url));
977 0 : ret = MHD_queue_response (god->sc.con,
978 : MHD_HTTP_FOUND,
979 : reply);
980 0 : MHD_destroy_response (reply);
981 0 : phase_end (god,
982 : ret);
983 0 : return;
984 : }
985 : /* Need to generate JSON reply */
986 0 : phase_end (god,
987 0 : TALER_MHD_REPLY_JSON_PACK (
988 : god->sc.con,
989 : MHD_HTTP_ACCEPTED,
990 : GNUNET_JSON_pack_string (
991 : "public_reorder_url",
992 : god->ct->public_reorder_url)));
993 0 : return;
994 : }
995 49 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
996 : "Claim token or contract matched\n");
997 49 : god->phase++;
998 : }
999 :
1000 :
1001 : /**
1002 : * Return the order summary of the contract of @a god in the
1003 : * preferred language of the HTTP client.
1004 : *
1005 : * @param god order to extract summary from
1006 : * @return dummy error message summary if no summary was provided in the contract
1007 : */
1008 : static const char *
1009 5 : get_order_summary (const struct GetOrderData *god)
1010 : {
1011 : const char *language_pattern;
1012 : const char *ret;
1013 : json_t *terms;
1014 :
1015 5 : if (NULL != god->contract_terms_json)
1016 3 : terms = god->contract_terms_json;
1017 : else
1018 2 : terms = god->order_json;
1019 5 : language_pattern = MHD_lookup_connection_value (
1020 5 : god->sc.con,
1021 : MHD_HEADER_KIND,
1022 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
1023 5 : if (NULL == language_pattern)
1024 5 : language_pattern = "en";
1025 5 : ret = json_string_value (TALER_JSON_extract_i18n (
1026 : terms,
1027 : language_pattern,
1028 : "summary"));
1029 5 : if (NULL == ret)
1030 : {
1031 : /* Upon order creation (and insertion into the database), the presence
1032 : of a summary should have been checked. So if we get here, someone
1033 : did something fishy to our database... */
1034 0 : GNUNET_break (0);
1035 0 : ret = "<bug: no summary>";
1036 : }
1037 5 : return ret;
1038 : }
1039 :
1040 :
1041 : /**
1042 : * Return the localized fulfillment message of the contract of @a god.
1043 : *
1044 : * @param god order to extract the fulfillment message from
1045 : * @return localized message, or NULL if no message was provided
1046 : */
1047 : static const char *
1048 1 : get_fulfillment_message (const struct GetOrderData *god)
1049 : {
1050 : const char *language_pattern;
1051 : const char *ret;
1052 : json_t *terms;
1053 :
1054 1 : if (NULL != god->contract_terms_json)
1055 1 : terms = god->contract_terms_json;
1056 : else
1057 0 : terms = god->order_json;
1058 1 : language_pattern = MHD_lookup_connection_value (
1059 1 : god->sc.con,
1060 : MHD_HEADER_KIND,
1061 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
1062 1 : if (NULL == language_pattern)
1063 1 : language_pattern = "en";
1064 1 : ret = json_string_value (TALER_JSON_extract_i18n (
1065 : terms,
1066 : language_pattern,
1067 : "fulfillment_message"));
1068 1 : if (NULL == ret)
1069 1 : ret = god->ct->fulfillment_message;
1070 1 : return ret;
1071 : }
1072 :
1073 :
1074 : /**
1075 : * Return the amount to display for the order. Before a version 1 order with
1076 : * multiple choices is claimed, the exact amount is selected by the wallet.
1077 : *
1078 : * @param god order to extract the amount from
1079 : * @param[out] multiple_payment_options set if the wallet selects the amount
1080 : * @return order amount, or NULL if no single amount is available
1081 : */
1082 : static const struct TALER_Amount *
1083 4 : get_order_amount (const struct GetOrderData *god,
1084 : bool *multiple_payment_options)
1085 : {
1086 4 : *multiple_payment_options = false;
1087 4 : switch (god->ct->version)
1088 : {
1089 2 : case TALER_MERCHANT_CONTRACT_VERSION_0:
1090 2 : return (NULL != god->pc)
1091 1 : ? &god->pc->details.v0.brutto
1092 2 : : &god->order->details.v0.brutto;
1093 2 : case TALER_MERCHANT_CONTRACT_VERSION_1:
1094 : {
1095 4 : unsigned int choices_len = (NULL != god->pc)
1096 1 : ? god->pc->details.v1.choices_len
1097 2 : : god->order->details.v1.choices_len;
1098 :
1099 2 : if (god->choice_index >= 0)
1100 : {
1101 0 : if (god->choice_index >= choices_len)
1102 : {
1103 0 : GNUNET_break (0);
1104 0 : return NULL;
1105 : }
1106 0 : return (NULL != god->pc)
1107 0 : ? &god->pc->details.v1.choices[god->choice_index].amount
1108 0 : : &god->order->details.v1.choices[god->choice_index].amount;
1109 : }
1110 2 : if (1 == choices_len)
1111 0 : return (NULL != god->pc)
1112 0 : ? &god->pc->details.v1.choices[0].amount
1113 0 : : &god->order->details.v1.choices[0].amount;
1114 2 : *multiple_payment_options = true;
1115 2 : return NULL;
1116 : }
1117 0 : default:
1118 0 : GNUNET_break (0);
1119 0 : return NULL;
1120 : }
1121 : }
1122 :
1123 :
1124 : /**
1125 : * The client did not yet pay, send it the payment request.
1126 : *
1127 : * @param god check pay request context
1128 : * @param already_paid_order_id if for the fulfillment URI there is
1129 : * already a paid order, this is the order ID to redirect
1130 : * the wallet to; NULL if not applicable
1131 : * @return true to exit due to suspension
1132 : */
1133 : static bool
1134 25 : send_pay_request (struct GetOrderData *god,
1135 : const char *already_paid_order_id)
1136 : {
1137 : enum MHD_Result ret;
1138 : char *taler_pay_uri;
1139 : char *order_status_url;
1140 : struct GNUNET_TIME_Relative remaining;
1141 :
1142 25 : remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
1143 25 : if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
1144 8 : (! god->generate_html) &&
1145 : (NULL == already_paid_order_id) )
1146 : {
1147 : /* long polling: do not queue a response, suspend connection instead */
1148 8 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1149 : "Suspending request: long polling for payment\n");
1150 8 : suspend_god (god);
1151 8 : return true;
1152 : }
1153 : /* Check if resource_id has been paid for in the same session
1154 : * with another order_id.
1155 : */
1156 17 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1157 : "Sending payment request\n");
1158 17 : taler_pay_uri = TMH_make_taler_pay_uri (
1159 : god->sc.con,
1160 : god->order_id,
1161 : god->session_id,
1162 17 : god->hc->instance->settings.id,
1163 : &god->claim_token);
1164 17 : order_status_url = TMH_make_order_status_url (
1165 : god->sc.con,
1166 : god->order_id,
1167 : god->session_id,
1168 17 : god->hc->instance->settings.id,
1169 : &god->claim_token,
1170 : NULL);
1171 17 : if ( (NULL == taler_pay_uri) ||
1172 : (NULL == order_status_url) )
1173 : {
1174 0 : GNUNET_break_op (0);
1175 0 : GNUNET_free (taler_pay_uri);
1176 0 : GNUNET_free (order_status_url);
1177 0 : phase_fail (god,
1178 : MHD_HTTP_BAD_REQUEST,
1179 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
1180 : "host");
1181 0 : return false;
1182 : }
1183 17 : if (god->generate_html)
1184 : {
1185 3 : if (NULL != already_paid_order_id)
1186 : {
1187 : struct MHD_Response *reply;
1188 :
1189 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1190 : "Redirecting to already paid order %s via fulfillment URL %s\n",
1191 : already_paid_order_id,
1192 : god->ct->fulfillment_url);
1193 0 : reply = MHD_create_response_from_buffer (0,
1194 : NULL,
1195 : MHD_RESPMEM_PERSISTENT);
1196 0 : if (NULL == reply)
1197 : {
1198 0 : GNUNET_break (0);
1199 0 : phase_end (god,
1200 : MHD_NO);
1201 0 : return false;
1202 : }
1203 0 : GNUNET_break (MHD_YES ==
1204 : MHD_add_response_header (
1205 : reply,
1206 : MHD_HTTP_HEADER_LOCATION,
1207 : god->ct->fulfillment_url));
1208 : {
1209 0 : ret = MHD_queue_response (god->sc.con,
1210 : MHD_HTTP_FOUND,
1211 : reply);
1212 0 : MHD_destroy_response (reply);
1213 0 : phase_end (god,
1214 : ret);
1215 0 : return false;
1216 : }
1217 : }
1218 :
1219 : {
1220 : char *qr;
1221 : bool multiple_payment_options;
1222 : const struct TALER_Amount *order_amount;
1223 : struct GNUNET_TIME_Timestamp pay_deadline;
1224 :
1225 3 : order_amount = get_order_amount (god,
1226 : &multiple_payment_options);
1227 6 : pay_deadline = (NULL != god->pc)
1228 1 : ? god->pc->pay_deadline
1229 3 : : god->order->pay_deadline;
1230 :
1231 3 : qr = TMH_create_qrcode (taler_pay_uri);
1232 3 : if (NULL == qr)
1233 : {
1234 0 : GNUNET_break (0);
1235 0 : phase_end (god,
1236 : MHD_NO);
1237 0 : return false;
1238 : }
1239 : {
1240 : enum GNUNET_GenericReturnValue res;
1241 : json_t *context;
1242 :
1243 3 : context = GNUNET_JSON_PACK (
1244 : GNUNET_JSON_pack_string ("taler_pay_uri",
1245 : taler_pay_uri),
1246 : GNUNET_JSON_pack_string ("order_status_url",
1247 : order_status_url),
1248 : GNUNET_JSON_pack_string ("taler_pay_qrcode_svg",
1249 : qr),
1250 : GNUNET_JSON_pack_string ("order_summary",
1251 : get_order_summary (god)),
1252 : GNUNET_JSON_pack_string ("order_id",
1253 : god->order_id),
1254 : GNUNET_JSON_pack_string ("merchant_name",
1255 : god->hc->instance->settings.name),
1256 : GNUNET_JSON_pack_allow_null (
1257 : TALER_JSON_pack_amount ("order_amount",
1258 : order_amount)),
1259 : GNUNET_JSON_pack_bool ("multiple_payment_options",
1260 : multiple_payment_options),
1261 : GNUNET_JSON_pack_bool ("claimed",
1262 : god->claimed),
1263 : GNUNET_JSON_pack_timestamp ("pay_deadline",
1264 : pay_deadline));
1265 3 : res = TALER_TEMPLATING_reply (
1266 : god->sc.con,
1267 : MHD_HTTP_PAYMENT_REQUIRED,
1268 : "request_payment",
1269 3 : god->hc->instance->settings.id,
1270 : taler_pay_uri,
1271 : context);
1272 3 : if (GNUNET_SYSERR == res)
1273 : {
1274 0 : GNUNET_break (0);
1275 0 : ret = MHD_NO;
1276 : }
1277 : else
1278 : {
1279 3 : ret = MHD_YES;
1280 : }
1281 3 : json_decref (context);
1282 : }
1283 3 : GNUNET_free (qr);
1284 : }
1285 : }
1286 : else /* end of 'generate HTML' */
1287 : {
1288 14 : ret = TALER_MHD_REPLY_JSON_PACK (
1289 : god->sc.con,
1290 : MHD_HTTP_PAYMENT_REQUIRED,
1291 : GNUNET_JSON_pack_string ("taler_pay_uri",
1292 : taler_pay_uri),
1293 : GNUNET_JSON_pack_allow_null (
1294 : GNUNET_JSON_pack_string ("fulfillment_url",
1295 : god->ct->fulfillment_url)),
1296 : GNUNET_JSON_pack_allow_null (
1297 : GNUNET_JSON_pack_string ("already_paid_order_id",
1298 : already_paid_order_id)));
1299 : }
1300 17 : GNUNET_free (taler_pay_uri);
1301 17 : GNUNET_free (order_status_url);
1302 17 : phase_end (god,
1303 : ret);
1304 17 : return false;
1305 : }
1306 :
1307 :
1308 : /**
1309 : * Check if the order has been paid.
1310 : *
1311 : * @param[in,out] god request context
1312 : */
1313 : static void
1314 49 : phase_check_paid (struct GetOrderData *god)
1315 : {
1316 : enum GNUNET_DB_QueryStatus qs;
1317 : struct TALER_PrivateContractHashP h_contract;
1318 :
1319 49 : god->paid = false;
1320 49 : qs = TALER_MERCHANTDB_get_order_status (
1321 : TMH_db,
1322 49 : god->hc->instance->settings.id,
1323 : god->order_id,
1324 : &h_contract,
1325 : &god->paid);
1326 49 : if (0 > qs)
1327 : {
1328 : /* Always report on hard error as well to enable diagnostics */
1329 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
1330 0 : phase_fail (god,
1331 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1332 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1333 : "get_order_status");
1334 0 : return;
1335 : }
1336 49 : god->phase++;
1337 : }
1338 :
1339 :
1340 : /**
1341 : * Check if the client already paid for an equivalent
1342 : * order under this session, and if so redirect to
1343 : * that order.
1344 : *
1345 : * @param[in,out] god request context
1346 : * @return true to exit due to suspension
1347 : */
1348 : static bool
1349 49 : phase_redirect_to_paid_order (struct GetOrderData *god)
1350 : {
1351 49 : if ( (NULL != god->session_id) &&
1352 10 : (NULL != god->ct->fulfillment_url) )
1353 : {
1354 : /* Check if client paid for this fulfillment article
1355 : already within this session, but using a different
1356 : order ID. If so, redirect the client to the order
1357 : it already paid. Allows, for example, the case
1358 : where a mobile phone pays for a browser's session,
1359 : where the mobile phone has a different order
1360 : ID (because it purchased the article earlier)
1361 : than the one that the browser is waiting for. */
1362 10 : char *already_paid_order_id = NULL;
1363 : enum GNUNET_DB_QueryStatus qs;
1364 :
1365 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1366 : "Running re-purchase detection for %s/%s\n",
1367 : god->session_id,
1368 : god->ct->fulfillment_url);
1369 10 : qs = TALER_MERCHANTDB_get_order_by_fulfillment (
1370 : TMH_db,
1371 10 : god->hc->instance->settings.id,
1372 10 : god->ct->fulfillment_url,
1373 : god->session_id,
1374 10 : TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase,
1375 : &already_paid_order_id);
1376 10 : if (qs < 0)
1377 : {
1378 : /* single, read-only SQL statements should never cause
1379 : serialization problems */
1380 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
1381 : /* Always report on hard error as well to enable diagnostics */
1382 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
1383 0 : phase_fail (god,
1384 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1385 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1386 : "order by fulfillment");
1387 8 : return false;
1388 : }
1389 10 : if ( (! god->paid) &&
1390 4 : ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
1391 4 : (0 != strcmp (god->order_id,
1392 : already_paid_order_id)) ) )
1393 : {
1394 : bool ret;
1395 :
1396 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1397 : "Sending pay request for order %s (already paid: %s)\n",
1398 : god->order_id,
1399 : already_paid_order_id);
1400 8 : ret = send_pay_request (god,
1401 : already_paid_order_id);
1402 8 : GNUNET_free (already_paid_order_id);
1403 8 : return ret;
1404 : }
1405 2 : GNUNET_free (already_paid_order_id);
1406 : }
1407 41 : god->phase++;
1408 41 : return false;
1409 : }
1410 :
1411 :
1412 : /**
1413 : * Check if the order has been paid, and if not
1414 : * request payment.
1415 : *
1416 : * @param[in,out] god request context
1417 : * @return true to exit due to suspension
1418 : */
1419 : static bool
1420 41 : phase_handle_unpaid (struct GetOrderData *god)
1421 : {
1422 41 : if (god->paid)
1423 : {
1424 24 : god->phase++;
1425 24 : return false;
1426 : }
1427 17 : if (god->claimed)
1428 : {
1429 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1430 : "Order claimed but unpaid, sending pay request for order %s\n",
1431 : god->order_id);
1432 : }
1433 : else
1434 : {
1435 7 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1436 : "Order unclaimed, sending pay request for order %s\n",
1437 : god->order_id);
1438 : }
1439 17 : return send_pay_request (god,
1440 : NULL);
1441 : }
1442 :
1443 :
1444 : /**
1445 : * Function called with detailed information about a refund.
1446 : * It is responsible for packing up the data to return.
1447 : *
1448 : * @param cls closure
1449 : * @param refund_serial unique serial number of the refund
1450 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
1451 : * @param coin_pub public coin from which the refund comes from
1452 : * @param exchange_url URL of the exchange that issued @a coin_pub
1453 : * @param rtransaction_id identificator of the refund
1454 : * @param reason human-readable explanation of the refund
1455 : * @param refund_amount refund amount which is being taken from @a coin_pub
1456 : * @param pending true if the this refund was not yet processed by the wallet/exchange
1457 : */
1458 : static void
1459 22 : process_refunds_cb (void *cls,
1460 : uint64_t refund_serial,
1461 : struct GNUNET_TIME_Timestamp timestamp,
1462 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
1463 : const char *exchange_url,
1464 : uint64_t rtransaction_id,
1465 : const char *reason,
1466 : const struct TALER_Amount *refund_amount,
1467 : bool pending)
1468 : {
1469 22 : struct GetOrderData *god = cls;
1470 :
1471 : (void) refund_serial;
1472 : (void) timestamp;
1473 : (void) exchange_url;
1474 : (void) rtransaction_id;
1475 22 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1476 : "Found refund of %s for coin %s with reason `%s' in database\n",
1477 : TALER_amount2s (refund_amount),
1478 : TALER_B2S (coin_pub),
1479 : reason);
1480 22 : god->refund_pending |= pending;
1481 22 : if ( (GNUNET_OK !=
1482 22 : TALER_amount_cmp_currency (&god->refund_taken,
1483 22 : refund_amount)) ||
1484 : (GNUNET_OK !=
1485 22 : TALER_amount_cmp_currency (&god->refund_amount,
1486 : refund_amount)) )
1487 : {
1488 0 : god->bad_refund_currency_in_db = true;
1489 0 : return;
1490 : }
1491 22 : if (! pending)
1492 : {
1493 4 : GNUNET_assert (0 <=
1494 : TALER_amount_add (&god->refund_taken,
1495 : &god->refund_taken,
1496 : refund_amount));
1497 : }
1498 22 : GNUNET_assert (0 <=
1499 : TALER_amount_add (&god->refund_amount,
1500 : &god->refund_amount,
1501 : refund_amount));
1502 22 : god->refunded = true;
1503 : }
1504 :
1505 :
1506 : /**
1507 : * Check if the order has been refunded.
1508 : *
1509 : * @param[in,out] god request context
1510 : * @return true to exit due to suspension
1511 : */
1512 : static bool
1513 24 : phase_check_refunded (struct GetOrderData *god)
1514 : {
1515 : enum GNUNET_DB_QueryStatus qs;
1516 : struct TALER_Amount refund_amount;
1517 : const char *refund_currency;
1518 :
1519 24 : GNUNET_assert (NULL != god->contract_terms);
1520 24 : switch (god->ct->version)
1521 : {
1522 24 : case TALER_MERCHANT_CONTRACT_VERSION_0:
1523 24 : refund_amount = god->pc->details.v0.brutto;
1524 24 : refund_currency = god->pc->details.v0.brutto.currency;
1525 24 : break;
1526 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
1527 0 : if (god->choice_index < 0)
1528 : {
1529 : // order was not paid, no refund to be checked
1530 0 : god->phase++;
1531 0 : return false;
1532 : }
1533 0 : GNUNET_assert (god->choice_index <
1534 : god->pc->details.v1.choices_len);
1535 0 : refund_currency = god->pc->details.v1.choices[
1536 0 : god->choice_index].amount.currency;
1537 0 : GNUNET_assert (GNUNET_OK ==
1538 : TALER_amount_set_zero (refund_currency,
1539 : &refund_amount));
1540 0 : break;
1541 0 : default:
1542 : {
1543 0 : GNUNET_break (0);
1544 0 : phase_fail (god,
1545 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1546 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
1547 : NULL);
1548 0 : return false;
1549 : }
1550 : }
1551 :
1552 30 : if ( (god->sc.awaiting_refund) &&
1553 : (GNUNET_OK !=
1554 6 : TALER_amount_cmp_currency (&refund_amount,
1555 6 : &god->sc.refund_expected)) )
1556 : {
1557 0 : GNUNET_break (0);
1558 0 : phase_fail (god,
1559 : MHD_HTTP_CONFLICT,
1560 : TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
1561 : refund_currency);
1562 0 : return false;
1563 : }
1564 :
1565 : /* At this point, we know the contract was paid. Let's check for
1566 : refunds. First, clear away refunds found from previous invocations. */
1567 24 : GNUNET_assert (GNUNET_OK ==
1568 : TALER_amount_set_zero (refund_currency,
1569 : &god->refund_amount));
1570 24 : GNUNET_assert (GNUNET_OK ==
1571 : TALER_amount_set_zero (refund_currency,
1572 : &god->refund_taken));
1573 24 : qs = TALER_MERCHANTDB_iterate_refunds_detailed (
1574 : TMH_db,
1575 24 : god->hc->instance->settings.id,
1576 24 : &god->h_contract_terms,
1577 : &process_refunds_cb,
1578 : god);
1579 24 : if (0 > qs)
1580 : {
1581 0 : GNUNET_break (0);
1582 0 : phase_fail (god,
1583 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1584 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1585 : "iterate_refunds_detailed");
1586 0 : return false;
1587 : }
1588 24 : if (god->bad_refund_currency_in_db)
1589 : {
1590 0 : GNUNET_break (0);
1591 0 : phase_fail (god,
1592 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1593 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1594 : "currency mix-up between contract price and refunds in database");
1595 0 : return false;
1596 : }
1597 24 : if ( ((god->sc.awaiting_refund) &&
1598 11 : ( (! god->refunded) ||
1599 5 : (1 != TALER_amount_cmp (&god->refund_amount,
1600 5 : &god->sc.refund_expected)) )) ||
1601 23 : ( (god->sc.awaiting_refund_obtained) &&
1602 0 : (god->refund_pending) ) )
1603 : {
1604 : /* Client is waiting for a refund larger than what we have, suspend
1605 : until timeout */
1606 : struct GNUNET_TIME_Relative remaining;
1607 :
1608 1 : remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
1609 1 : if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
1610 1 : (! god->generate_html) )
1611 : {
1612 : /* yes, indeed suspend */
1613 1 : if (god->sc.awaiting_refund)
1614 1 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1615 : "Awaiting refund exceeding %s\n",
1616 : TALER_amount2s (&god->sc.refund_expected));
1617 1 : if (god->sc.awaiting_refund_obtained)
1618 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1619 : "Awaiting pending refunds\n");
1620 1 : suspend_god (god);
1621 1 : return true;
1622 : }
1623 : }
1624 23 : god->phase++;
1625 23 : return false;
1626 : }
1627 :
1628 :
1629 : /**
1630 : * Create a taler://refund/ URI for the given @a con and @a order_id
1631 : * and @a instance_id.
1632 : *
1633 : * @param merchant_base_url URL to take host and path from;
1634 : * we cannot take it from the MHD connection as a browser
1635 : * may have changed 'http' to 'https' and we MUST be consistent
1636 : * with what the merchant's frontend used initially
1637 : * @param order_id the order id
1638 : * @return corresponding taler://refund/ URI, or NULL on missing "host"
1639 : */
1640 : static char *
1641 1 : make_taler_refund_uri (const char *merchant_base_url,
1642 : const char *order_id)
1643 : {
1644 1 : struct GNUNET_Buffer buf = { 0 };
1645 : char *url;
1646 : struct GNUNET_Uri uri;
1647 :
1648 1 : url = GNUNET_strdup (merchant_base_url);
1649 1 : if (-1 == GNUNET_uri_parse (&uri,
1650 : url))
1651 : {
1652 0 : GNUNET_break (0);
1653 0 : GNUNET_free (url);
1654 0 : return NULL;
1655 : }
1656 1 : GNUNET_assert (NULL != order_id);
1657 1 : GNUNET_buffer_write_str (&buf,
1658 : "taler");
1659 1 : if (0 == strcasecmp ("http",
1660 1 : uri.scheme))
1661 1 : GNUNET_buffer_write_str (&buf,
1662 : "+http");
1663 1 : GNUNET_buffer_write_str (&buf,
1664 : "://refund/");
1665 1 : GNUNET_buffer_write_str (&buf,
1666 1 : uri.host);
1667 1 : if (0 != uri.port)
1668 1 : GNUNET_buffer_write_fstr (&buf,
1669 : ":%u",
1670 1 : (unsigned int) uri.port);
1671 1 : if (NULL != uri.path)
1672 1 : GNUNET_buffer_write_path (&buf,
1673 1 : uri.path);
1674 1 : GNUNET_buffer_write_path (&buf,
1675 : order_id);
1676 1 : GNUNET_buffer_write_path (&buf,
1677 : ""); // Trailing slash
1678 1 : GNUNET_free (url);
1679 1 : return GNUNET_buffer_reap_str (&buf);
1680 : }
1681 :
1682 :
1683 : /**
1684 : * Generate the order status response.
1685 : *
1686 : * @param[in,out] god request context
1687 : */
1688 : static void
1689 23 : phase_return_status (struct GetOrderData *god)
1690 : {
1691 : /* All operations done, build final response */
1692 23 : if (! god->generate_html)
1693 : {
1694 21 : phase_end (god,
1695 21 : TALER_MHD_REPLY_JSON_PACK (
1696 : god->sc.con,
1697 : MHD_HTTP_OK,
1698 : GNUNET_JSON_pack_allow_null (
1699 : GNUNET_JSON_pack_string ("fulfillment_url",
1700 : god->ct->fulfillment_url)),
1701 : GNUNET_JSON_pack_bool ("refunded",
1702 : god->refunded),
1703 : GNUNET_JSON_pack_bool ("refund_pending",
1704 : god->refund_pending),
1705 : TALER_JSON_pack_amount ("refund_taken",
1706 : &god->refund_taken),
1707 : TALER_JSON_pack_amount ("refund_amount",
1708 : &god->refund_amount)));
1709 21 : return;
1710 : }
1711 :
1712 2 : if (god->refund_pending)
1713 : {
1714 : struct TALER_Amount refund_remaining;
1715 : char *qr;
1716 : char *uri;
1717 : char *order_status_url;
1718 :
1719 1 : GNUNET_assert (NULL != god->contract_terms_json);
1720 1 : GNUNET_assert (NULL != god->contract_terms);
1721 1 : GNUNET_assert (0 <=
1722 : TALER_amount_subtract (&refund_remaining,
1723 : &god->refund_amount,
1724 : &god->refund_taken));
1725 1 : uri = make_taler_refund_uri (god->pc->merchant_base_url,
1726 : god->order_id);
1727 1 : order_status_url = TMH_make_order_status_url (
1728 : god->sc.con,
1729 : god->order_id,
1730 : god->session_id,
1731 1 : god->hc->instance->settings.id,
1732 : NULL,
1733 : &god->h_contract_terms);
1734 1 : if ( (NULL == uri) ||
1735 : (NULL == order_status_url) )
1736 : {
1737 0 : GNUNET_break (0);
1738 0 : GNUNET_free (uri);
1739 0 : GNUNET_free (order_status_url);
1740 0 : phase_fail (god,
1741 : MHD_HTTP_BAD_REQUEST,
1742 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
1743 : "host");
1744 0 : return;
1745 : }
1746 1 : qr = TMH_create_qrcode (uri);
1747 1 : if (NULL == qr)
1748 : {
1749 0 : GNUNET_break (0);
1750 0 : GNUNET_free (uri);
1751 0 : GNUNET_free (order_status_url);
1752 0 : phase_fail (god,
1753 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1754 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1755 : "qr code");
1756 0 : return;
1757 : }
1758 :
1759 : {
1760 : enum GNUNET_GenericReturnValue res;
1761 : json_t *context;
1762 :
1763 1 : context = GNUNET_JSON_PACK (
1764 : GNUNET_JSON_pack_string ("order_summary",
1765 : get_order_summary (god)),
1766 : TALER_JSON_pack_amount ("refund_amount",
1767 : &god->refund_amount),
1768 : TALER_JSON_pack_amount ("refund_taken",
1769 : &god->refund_taken),
1770 : TALER_JSON_pack_amount ("refund_remaining",
1771 : &refund_remaining),
1772 : GNUNET_JSON_pack_bool ("has_refund_taken",
1773 : ! TALER_amount_is_zero (
1774 : &god->refund_taken)),
1775 : GNUNET_JSON_pack_string ("order_id",
1776 : god->order_id),
1777 : GNUNET_JSON_pack_string ("merchant_name",
1778 : god->hc->instance->settings.name),
1779 : GNUNET_JSON_pack_string ("order_status_url",
1780 : order_status_url),
1781 : GNUNET_JSON_pack_string ("taler_refund_uri",
1782 : uri),
1783 : GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
1784 : qr));
1785 1 : res = TALER_TEMPLATING_reply (
1786 : god->sc.con,
1787 : MHD_HTTP_OK,
1788 : "offer_refund",
1789 1 : god->hc->instance->settings.id,
1790 : uri,
1791 : context);
1792 1 : GNUNET_break (GNUNET_OK == res);
1793 1 : json_decref (context);
1794 1 : phase_end (god,
1795 : (GNUNET_SYSERR == res)
1796 : ? MHD_NO
1797 : : MHD_YES);
1798 : }
1799 1 : GNUNET_free (uri);
1800 1 : GNUNET_free (order_status_url);
1801 1 : GNUNET_free (qr);
1802 1 : return;
1803 : }
1804 :
1805 : {
1806 : enum GNUNET_GenericReturnValue res;
1807 : bool multiple_payment_options;
1808 : const struct TALER_Amount *order_amount;
1809 : json_t *context;
1810 : const char *fulfillment_message;
1811 :
1812 1 : order_amount = get_order_amount (god,
1813 : &multiple_payment_options);
1814 1 : GNUNET_break (! multiple_payment_options);
1815 1 : fulfillment_message = get_fulfillment_message (god);
1816 :
1817 1 : context = GNUNET_JSON_PACK (
1818 : GNUNET_JSON_pack_object_incref ("contract_terms",
1819 : NULL != god->contract_terms_json
1820 : ? god->contract_terms_json
1821 : : god->order_json),
1822 : GNUNET_JSON_pack_string ("order_summary",
1823 : get_order_summary (god)),
1824 : TALER_JSON_pack_amount ("refund_amount",
1825 : &god->refund_amount),
1826 : TALER_JSON_pack_amount ("refund_taken",
1827 : &god->refund_taken),
1828 : GNUNET_JSON_pack_allow_null (
1829 : TALER_JSON_pack_amount ("order_amount",
1830 : order_amount)),
1831 : GNUNET_JSON_pack_string ("order_id",
1832 : god->order_id),
1833 : GNUNET_JSON_pack_string ("merchant_name",
1834 : god->hc->instance->settings.name),
1835 : GNUNET_JSON_pack_bool ("has_refund",
1836 : god->refunded),
1837 : GNUNET_JSON_pack_allow_null (
1838 : GNUNET_JSON_pack_string ("fulfillment_message",
1839 : fulfillment_message)),
1840 : GNUNET_JSON_pack_allow_null (
1841 : GNUNET_JSON_pack_string ("fulfillment_url",
1842 : god->ct->fulfillment_url)));
1843 1 : res = TALER_TEMPLATING_reply (
1844 : god->sc.con,
1845 : MHD_HTTP_OK,
1846 : "show_order_details",
1847 1 : god->hc->instance->settings.id,
1848 : NULL,
1849 : context);
1850 1 : GNUNET_break (GNUNET_OK == res);
1851 1 : json_decref (context);
1852 1 : phase_end (god,
1853 : (GNUNET_SYSERR == res)
1854 : ? MHD_NO
1855 : : MHD_YES);
1856 : }
1857 : }
1858 :
1859 :
1860 : enum MHD_Result
1861 49 : TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
1862 : struct MHD_Connection *connection,
1863 : struct TMH_HandlerContext *hc)
1864 : {
1865 49 : struct GetOrderData *god = hc->ctx;
1866 :
1867 : (void) rh;
1868 49 : if (NULL == god)
1869 : {
1870 40 : god = GNUNET_new (struct GetOrderData);
1871 40 : god->choice_index = -1;
1872 40 : hc->ctx = god;
1873 40 : hc->cc = &god_cleanup;
1874 40 : god->sc.con = connection;
1875 40 : god->hc = hc;
1876 40 : god->order_id = hc->infix;
1877 : god->generate_html
1878 40 : = TMH_MHD_test_html_desired (connection);
1879 :
1880 : /* first-time initialization / sanity checks */
1881 40 : TALER_MHD_parse_request_arg_auto (connection,
1882 : "h_contract",
1883 : &god->h_contract_terms,
1884 : god->h_contract_provided);
1885 40 : TALER_MHD_parse_request_arg_auto (connection,
1886 : "token",
1887 : &god->claim_token,
1888 : god->claim_token_provided);
1889 40 : if (! (TALER_MHD_arg_to_yna (connection,
1890 : "allow_refunded_for_repurchase",
1891 : TALER_EXCHANGE_YNA_NO,
1892 : &god->allow_refunded_for_repurchase)) )
1893 0 : return TALER_MHD_reply_with_error (connection,
1894 : MHD_HTTP_BAD_REQUEST,
1895 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1896 : "allow_refunded_for_repurchase");
1897 40 : god->session_id = MHD_lookup_connection_value (connection,
1898 : MHD_GET_ARGUMENT_KIND,
1899 : "session_id");
1900 :
1901 : /* process await_refund_obtained argument */
1902 : {
1903 : const char *await_refund_obtained_s;
1904 :
1905 : await_refund_obtained_s =
1906 40 : MHD_lookup_connection_value (connection,
1907 : MHD_GET_ARGUMENT_KIND,
1908 : "await_refund_obtained");
1909 40 : god->sc.awaiting_refund_obtained =
1910 : (NULL != await_refund_obtained_s)
1911 0 : ? 0 == strcasecmp (await_refund_obtained_s,
1912 : "yes")
1913 40 : : false;
1914 40 : if (god->sc.awaiting_refund_obtained)
1915 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1916 : "Awaiting refund obtained\n");
1917 : }
1918 :
1919 40 : TALER_MHD_parse_request_amount (connection,
1920 : "refund",
1921 : &god->sc.refund_expected);
1922 40 : if (TALER_amount_is_valid (&god->sc.refund_expected))
1923 : {
1924 5 : god->sc.awaiting_refund = true;
1925 5 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1926 : "Awaiting minimum refund of %s\n",
1927 : TALER_amount2s (&god->sc.refund_expected));
1928 : }
1929 40 : TALER_MHD_parse_request_timeout (connection,
1930 : &god->sc.long_poll_timeout);
1931 : }
1932 :
1933 49 : if (GNUNET_SYSERR == god->suspended)
1934 0 : return MHD_NO; /* we are in shutdown */
1935 49 : if (GNUNET_YES == god->suspended)
1936 : {
1937 0 : god->suspended = GNUNET_NO;
1938 0 : GNUNET_CONTAINER_DLL_remove (god_head,
1939 : god_tail,
1940 : god);
1941 : }
1942 :
1943 : while (1)
1944 : {
1945 777 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1946 : "Handling request in phase %d\n",
1947 : (int) god->phase);
1948 413 : switch (god->phase)
1949 : {
1950 40 : case GOP_INIT:
1951 40 : phase_init (god);
1952 40 : break;
1953 49 : case GOP_LOOKUP_TERMS:
1954 49 : phase_lookup_terms (god);
1955 49 : break;
1956 49 : case GOP_PARSE_CONTRACT:
1957 49 : phase_parse_contract (god);
1958 49 : break;
1959 49 : case GOP_CHECK_CLIENT_ACCESS:
1960 49 : phase_check_client_access (god);
1961 49 : break;
1962 49 : case GOP_CHECK_PAID:
1963 49 : phase_check_paid (god);
1964 49 : break;
1965 49 : case GOP_REDIRECT_TO_PAID_ORDER:
1966 49 : if (phase_redirect_to_paid_order (god))
1967 2 : return MHD_YES;
1968 47 : break;
1969 41 : case GOP_HANDLE_UNPAID:
1970 41 : if (phase_handle_unpaid (god))
1971 6 : return MHD_YES;
1972 35 : break;
1973 24 : case GOP_CHECK_REFUNDED:
1974 24 : if (phase_check_refunded (god))
1975 1 : return MHD_YES;
1976 23 : break;
1977 23 : case GOP_RETURN_STATUS:
1978 23 : phase_return_status (god);
1979 23 : break;
1980 40 : case GOP_RETURN_MHD_YES:
1981 40 : return MHD_YES;
1982 0 : case GOP_RETURN_MHD_NO:
1983 0 : return MHD_NO;
1984 : }
1985 : }
1986 : }
1987 :
1988 :
1989 : /* end of taler-merchant-httpd_get-orders-ORDER_ID.c */
|