Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2017-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU 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_private-get-orders-ID.c
18 : * @brief implementation of GET /private/orders/ID handler
19 : * @author Florian Dold
20 : * @author Christian Grothoff
21 : * @author Bohdan Potuzhnyi
22 : * @author Iván Ávalos
23 : */
24 : #include "platform.h"
25 : #include <taler/taler_json_lib.h>
26 : #include <taler/taler_dbevents.h>
27 : #include <taler/taler_error_codes.h>
28 : #include <taler/taler_util.h>
29 : #include <gnunet/gnunet_common.h>
30 : #include <gnunet/gnunet_json_lib.h>
31 : #include "taler_merchant_util.h"
32 : #include "taler-merchant-httpd_helper.h"
33 : #include "taler-merchant-httpd_private-get-orders.h"
34 : #include "taler-merchant-httpd_private-get-orders-ID.h"
35 :
36 : /**
37 : * Data structure we keep for a check payment request.
38 : */
39 : struct GetOrderRequestContext;
40 :
41 :
42 : /**
43 : * Request to an exchange for details about wire transfers
44 : * in response to a coin's deposit operation.
45 : */
46 : struct TransferQuery
47 : {
48 :
49 : /**
50 : * Kept in a DLL.
51 : */
52 : struct TransferQuery *next;
53 :
54 : /**
55 : * Kept in a DLL.
56 : */
57 : struct TransferQuery *prev;
58 :
59 : /**
60 : * Base URL of the exchange.
61 : */
62 : char *exchange_url;
63 :
64 : /**
65 : * Overall request this TQ belongs with.
66 : */
67 : struct GetOrderRequestContext *gorc;
68 :
69 : /**
70 : * Hash of the merchant's bank account the transfer (presumably) went to.
71 : */
72 : struct TALER_MerchantWireHashP h_wire;
73 :
74 : /**
75 : * Value deposited (including deposit fee).
76 : */
77 : struct TALER_Amount amount_with_fee;
78 :
79 : /**
80 : * Deposit fee paid for this coin.
81 : */
82 : struct TALER_Amount deposit_fee;
83 :
84 : /**
85 : * Public key of the coin this is about.
86 : */
87 : struct TALER_CoinSpendPublicKeyP coin_pub;
88 :
89 : /**
90 : * Which deposit operation is this about?
91 : */
92 : uint64_t deposit_serial;
93 :
94 : };
95 :
96 :
97 : /**
98 : * Phases of order processing.
99 : */
100 : enum GetOrderPhase
101 : {
102 : /**
103 : * Initialization.
104 : */
105 : GOP_INIT = 0,
106 :
107 : /**
108 : * Obtain contract terms from database.
109 : */
110 : GOP_FETCH_CONTRACT = 1,
111 :
112 : /**
113 : * Parse the contract terms.
114 : */
115 : GOP_PARSE_CONTRACT = 2,
116 :
117 : /**
118 : * Check if the contract was fully paid.
119 : */
120 : GOP_CHECK_PAID = 3,
121 :
122 : /**
123 : * Check if the wallet may have purchased an equivalent
124 : * order before and we need to redirect the wallet to
125 : * an existing paid order.
126 : */
127 : GOP_CHECK_REPURCHASE = 4,
128 :
129 : /**
130 : * Terminate processing of unpaid orders, either by
131 : * suspending until payment or by returning the
132 : * unpaid order status.
133 : */
134 : GOP_UNPAID_FINISH = 5,
135 :
136 : /**
137 : * Check if the (paid) order was refunded.
138 : */
139 : GOP_CHECK_REFUNDS = 6,
140 :
141 : /**
142 : * Load all deposits associated with the order.
143 : */
144 : GOP_CHECK_DEPOSITS = 7,
145 :
146 : /**
147 : * Check local records for transfers of funds to
148 : * the merchant.
149 : */
150 : GOP_CHECK_LOCAL_TRANSFERS = 8,
151 :
152 : /**
153 : * Generate final comprehensive result.
154 : */
155 : GOP_REPLY_RESULT = 9,
156 :
157 : /**
158 : * End with the HTTP status and error code in
159 : * wire_hc and wire_ec.
160 : */
161 : GOP_ERROR = 10,
162 :
163 : /**
164 : * We are suspended awaiting payment.
165 : */
166 : GOP_SUSPENDED_ON_UNPAID = 11,
167 :
168 : /**
169 : * Processing is done, return #MHD_YES.
170 : */
171 : GOP_END_YES = 12,
172 :
173 : /**
174 : * Processing is done, return #MHD_NO.
175 : */
176 : GOP_END_NO = 13
177 :
178 : };
179 :
180 :
181 : /**
182 : * Data structure we keep for a check payment request.
183 : */
184 : struct GetOrderRequestContext
185 : {
186 :
187 : /**
188 : * Processing phase we are in.
189 : */
190 : enum GetOrderPhase phase;
191 :
192 : /**
193 : * Entry in the #resume_timeout_heap for this check payment, if we are
194 : * suspended.
195 : */
196 : struct TMH_SuspendedConnection sc;
197 :
198 : /**
199 : * Which merchant instance is this for?
200 : */
201 : struct TMH_HandlerContext *hc;
202 :
203 : /**
204 : * session of the client
205 : */
206 : const char *session_id;
207 :
208 : /**
209 : * Kept in a DLL while suspended on exchange.
210 : */
211 : struct GetOrderRequestContext *next;
212 :
213 : /**
214 : * Kept in a DLL while suspended on exchange.
215 : */
216 : struct GetOrderRequestContext *prev;
217 :
218 : /**
219 : * Head of DLL of individual queries for transfer data.
220 : */
221 : struct TransferQuery *tq_head;
222 :
223 : /**
224 : * Tail of DLL of individual queries for transfer data.
225 : */
226 : struct TransferQuery *tq_tail;
227 :
228 : /**
229 : * Timeout task while waiting on exchange.
230 : */
231 : struct GNUNET_SCHEDULER_Task *tt;
232 :
233 : /**
234 : * Database event we are waiting on to be resuming
235 : * for payment or refunds.
236 : */
237 : struct GNUNET_DB_EventHandler *eh;
238 :
239 : /**
240 : * Database event we are waiting on to be resuming
241 : * for session capture.
242 : */
243 : struct GNUNET_DB_EventHandler *session_eh;
244 :
245 : /**
246 : * Contract terms of the payment we are checking. NULL when they
247 : * are not (yet) known.
248 : */
249 : json_t *contract_terms_json;
250 :
251 : /**
252 : * Parsed contract terms, NULL when parsing failed
253 : */
254 : struct TALER_MERCHANT_Contract *contract_terms;
255 :
256 : /**
257 : * Claim token of the order.
258 : */
259 : struct TALER_ClaimTokenP claim_token;
260 :
261 : /**
262 : * Timestamp of the last payment.
263 : */
264 : struct GNUNET_TIME_Timestamp last_payment;
265 :
266 : /**
267 : * Wire details for the payment, to be returned in the reply. NULL
268 : * if not available.
269 : */
270 : json_t *wire_details;
271 :
272 : /**
273 : * Details about refunds, NULL if there are no refunds.
274 : */
275 : json_t *refund_details;
276 :
277 : /**
278 : * Amount of the order, unset for unpaid v1 orders.
279 : */
280 : struct TALER_Amount contract_amount;
281 :
282 : /**
283 : * Hash over the @e contract_terms.
284 : */
285 : struct TALER_PrivateContractHashP h_contract_terms;
286 :
287 : /**
288 : * Total amount the exchange deposited into our bank account
289 : * (confirmed or unconfirmed), excluding fees.
290 : */
291 : struct TALER_Amount deposits_total;
292 :
293 : /**
294 : * Total amount in deposit fees we paid for all coins.
295 : */
296 : struct TALER_Amount deposit_fees_total;
297 :
298 : /**
299 : * Total value of the coins that the exchange deposited into our bank
300 : * account (confirmed or unconfirmed), including deposit fees.
301 : */
302 : struct TALER_Amount value_total;
303 :
304 : /**
305 : * Serial ID of the order.
306 : */
307 : uint64_t order_serial;
308 :
309 : /**
310 : * Index of selected choice from ``choices`` array in the contract_terms.
311 : * Is -1 for orders without choices.
312 : */
313 : int16_t choice_index;
314 :
315 : /**
316 : * Total refunds granted for this payment. Only initialized
317 : * if @e refunded is set to true.
318 : */
319 : struct TALER_Amount refund_amount;
320 :
321 : /**
322 : * Exchange HTTP error code encountered while trying to determine wire transfer
323 : * details. #TALER_EC_NONE for no error encountered.
324 : */
325 : unsigned int exchange_hc;
326 :
327 : /**
328 : * Exchange error code encountered while trying to determine wire transfer
329 : * details. #TALER_EC_NONE for no error encountered.
330 : */
331 : enum TALER_ErrorCode exchange_ec;
332 :
333 : /**
334 : * Error code encountered while trying to determine wire transfer
335 : * details. #TALER_EC_NONE for no error encountered.
336 : */
337 : enum TALER_ErrorCode wire_ec;
338 :
339 : /**
340 : * Set to YES if refunded orders should be included when
341 : * doing repurchase detection.
342 : */
343 : enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
344 :
345 : /**
346 : * HTTP status to return with @e wire_ec, 0 if @e wire_ec is #TALER_EC_NONE.
347 : */
348 : unsigned int wire_hc;
349 :
350 : /**
351 : * Did we suspend @a connection and are thus in
352 : * the #gorc_head DLL (#GNUNET_YES). Set to
353 : * #GNUNET_NO if we are not suspended, and to
354 : * #GNUNET_SYSERR if we should close the connection
355 : * without a response due to shutdown.
356 : */
357 : enum GNUNET_GenericReturnValue suspended;
358 :
359 : /**
360 : * Set to true if this payment has been refunded and
361 : * @e refund_amount is initialized.
362 : */
363 : bool refunded;
364 :
365 : /**
366 : * True if the order was paid.
367 : */
368 : bool paid;
369 :
370 : /**
371 : * True if the paid session in the database matches
372 : * our @e session_id.
373 : */
374 : bool paid_session_matches;
375 :
376 : /**
377 : * True if the exchange wired the money to the merchant.
378 : */
379 : bool wired;
380 :
381 : /**
382 : * True if the order remains unclaimed.
383 : */
384 : bool order_only;
385 :
386 : /**
387 : * Set to true if this payment has been refunded and
388 : * some refunds remain to be picked up by the wallet.
389 : */
390 : bool refund_pending;
391 :
392 : /**
393 : * Set to true if our database (incorrectly) has refunds
394 : * in a different currency than the currency of the
395 : * original payment for the order.
396 : */
397 : bool refund_currency_mismatch;
398 :
399 : /**
400 : * Set to true if our database (incorrectly) has deposits
401 : * in a different currency than the currency of the
402 : * original payment for the order.
403 : */
404 : bool deposit_currency_mismatch;
405 : };
406 :
407 :
408 : /**
409 : * Head of list of suspended requests waiting on the exchange.
410 : */
411 : static struct GetOrderRequestContext *gorc_head;
412 :
413 : /**
414 : * Tail of list of suspended requests waiting on the exchange.
415 : */
416 : static struct GetOrderRequestContext *gorc_tail;
417 :
418 :
419 : void
420 15 : TMH_force_gorc_resume (void)
421 : {
422 : struct GetOrderRequestContext *gorc;
423 :
424 15 : while (NULL != (gorc = gorc_head))
425 : {
426 0 : GNUNET_CONTAINER_DLL_remove (gorc_head,
427 : gorc_tail,
428 : gorc);
429 0 : GNUNET_assert (GNUNET_YES == gorc->suspended);
430 0 : gorc->suspended = GNUNET_SYSERR;
431 0 : MHD_resume_connection (gorc->sc.con);
432 : }
433 15 : }
434 :
435 :
436 : /**
437 : * We have received a trigger from the database
438 : * that we should (possibly) resume the request.
439 : *
440 : * @param cls a `struct GetOrderRequestContext` to resume
441 : * @param extra string encoding refund amount (or NULL)
442 : * @param extra_size number of bytes in @a extra
443 : */
444 : static void
445 2 : resume_by_event (void *cls,
446 : const void *extra,
447 : size_t extra_size)
448 : {
449 2 : struct GetOrderRequestContext *gorc = cls;
450 :
451 : (void) extra;
452 : (void) extra_size;
453 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
454 : "Resuming request for order %s by trigger\n",
455 : gorc->hc->infix);
456 2 : if (GNUNET_NO == gorc->suspended)
457 0 : return; /* duplicate event is possible */
458 2 : gorc->suspended = GNUNET_NO;
459 2 : gorc->phase = GOP_FETCH_CONTRACT;
460 2 : GNUNET_CONTAINER_DLL_remove (gorc_head,
461 : gorc_tail,
462 : gorc);
463 2 : MHD_resume_connection (gorc->sc.con);
464 2 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
465 : }
466 :
467 :
468 : /**
469 : * Clean up the session state for a GET /private/order/ID request.
470 : *
471 : * @param cls closure, must be a `struct GetOrderRequestContext *`
472 : */
473 : static void
474 62 : gorc_cleanup (void *cls)
475 : {
476 62 : struct GetOrderRequestContext *gorc = cls;
477 : struct TransferQuery *tq;
478 :
479 98 : while (NULL != (tq = gorc->tq_head))
480 : {
481 36 : GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
482 : gorc->tq_tail,
483 : tq);
484 36 : GNUNET_free (tq->exchange_url);
485 36 : GNUNET_free (tq);
486 : }
487 :
488 62 : if (NULL != gorc->contract_terms_json)
489 62 : json_decref (gorc->contract_terms_json);
490 62 : if (NULL != gorc->contract_terms)
491 : {
492 62 : TALER_MERCHANT_contract_free (gorc->contract_terms);
493 62 : gorc->contract_terms = NULL;
494 : }
495 62 : if (NULL != gorc->wire_details)
496 26 : json_decref (gorc->wire_details);
497 62 : if (NULL != gorc->refund_details)
498 26 : json_decref (gorc->refund_details);
499 62 : if (NULL != gorc->tt)
500 : {
501 0 : GNUNET_SCHEDULER_cancel (gorc->tt);
502 0 : gorc->tt = NULL;
503 : }
504 62 : if (NULL != gorc->eh)
505 : {
506 4 : TMH_db->event_listen_cancel (gorc->eh);
507 4 : gorc->eh = NULL;
508 : }
509 62 : if (NULL != gorc->session_eh)
510 : {
511 0 : TMH_db->event_listen_cancel (gorc->session_eh);
512 0 : gorc->session_eh = NULL;
513 : }
514 62 : GNUNET_free (gorc);
515 62 : }
516 :
517 :
518 : /**
519 : * Processing the request @a gorc is finished, set the
520 : * final return value in phase based on @a mret.
521 : *
522 : * @param[in,out] gorc order context to initialize
523 : * @param mret MHD HTTP response status to return
524 : */
525 : static void
526 62 : phase_end (struct GetOrderRequestContext *gorc,
527 : MHD_RESULT mret)
528 : {
529 62 : gorc->phase = (MHD_YES == mret)
530 : ? GOP_END_YES
531 62 : : GOP_END_NO;
532 62 : }
533 :
534 :
535 : /**
536 : * Initialize event callbacks for the order processing.
537 : *
538 : * @param[in,out] gorc order context to initialize
539 : */
540 : static void
541 62 : phase_init (struct GetOrderRequestContext *gorc)
542 : {
543 62 : struct TMH_HandlerContext *hc = gorc->hc;
544 62 : struct TMH_OrderPayEventP pay_eh = {
545 62 : .header.size = htons (sizeof (pay_eh)),
546 62 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
547 62 : .merchant_pub = hc->instance->merchant_pub
548 : };
549 :
550 62 : if (! GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
551 : {
552 58 : gorc->phase++;
553 58 : return;
554 : }
555 :
556 4 : GNUNET_CRYPTO_hash (hc->infix,
557 4 : strlen (hc->infix),
558 : &pay_eh.h_order_id);
559 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
560 : "Subscribing to payment triggers for %p\n",
561 : gorc);
562 4 : gorc->eh = TMH_db->event_listen (
563 4 : TMH_db->cls,
564 : &pay_eh.header,
565 : GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
566 : &resume_by_event,
567 : gorc);
568 4 : if ( (NULL != gorc->session_id) &&
569 0 : (NULL != gorc->contract_terms->fulfillment_url) )
570 : {
571 0 : struct TMH_SessionEventP session_eh = {
572 0 : .header.size = htons (sizeof (session_eh)),
573 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
574 0 : .merchant_pub = hc->instance->merchant_pub
575 : };
576 :
577 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
578 : "Subscribing to session triggers for %p\n",
579 : gorc);
580 0 : GNUNET_CRYPTO_hash (gorc->session_id,
581 : strlen (gorc->session_id),
582 : &session_eh.h_session_id);
583 0 : GNUNET_CRYPTO_hash (gorc->contract_terms->fulfillment_url,
584 0 : strlen (gorc->contract_terms->fulfillment_url),
585 : &session_eh.h_fulfillment_url);
586 : gorc->session_eh
587 0 : = TMH_db->event_listen (
588 0 : TMH_db->cls,
589 : &session_eh.header,
590 : GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
591 : &resume_by_event,
592 : gorc);
593 : }
594 4 : gorc->phase++;
595 : }
596 :
597 :
598 : /**
599 : * Obtain latest contract terms from the database.
600 : *
601 : * @param[in,out] gorc order context to update
602 : */
603 : static void
604 64 : phase_fetch_contract (struct GetOrderRequestContext *gorc)
605 : {
606 64 : struct TMH_HandlerContext *hc = gorc->hc;
607 : enum GNUNET_DB_QueryStatus qs;
608 :
609 64 : if (NULL != gorc->contract_terms_json)
610 : {
611 : /* Free memory filled with old contract terms before fetching the latest
612 : ones from the DB. Note that we cannot simply skip the database
613 : interaction as the contract terms loaded previously might be from an
614 : earlier *unclaimed* order state (which we loaded in a previous
615 : invocation of this function and we are back here due to long polling)
616 : and thus the contract terms could have changed during claiming. Thus,
617 : we need to fetch the latest contract terms from the DB again. */
618 2 : json_decref (gorc->contract_terms_json);
619 2 : gorc->contract_terms_json = NULL;
620 2 : gorc->order_only = false;
621 : }
622 64 : TMH_db->preflight (TMH_db->cls);
623 64 : qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
624 64 : hc->instance->settings.id,
625 64 : hc->infix,
626 : gorc->session_id,
627 : &gorc->contract_terms_json,
628 : &gorc->order_serial,
629 : &gorc->paid,
630 : &gorc->wired,
631 : &gorc->paid_session_matches,
632 : &gorc->claim_token,
633 : &gorc->choice_index);
634 64 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
635 : "lookup_contract_terms (%s) returned %d\n",
636 : hc->infix,
637 : (int) qs);
638 64 : if (0 > qs)
639 : {
640 : /* single, read-only SQL statements should never cause
641 : serialization problems */
642 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
643 : /* Always report on hard error as well to enable diagnostics */
644 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
645 0 : phase_end (gorc,
646 : TALER_MHD_reply_with_error (gorc->sc.con,
647 : MHD_HTTP_INTERNAL_SERVER_ERROR,
648 : TALER_EC_GENERIC_DB_FETCH_FAILED,
649 : "contract terms"));
650 0 : return;
651 : }
652 64 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
653 : {
654 52 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
655 : "Order %s is %s (%s) according to database\n",
656 : hc->infix,
657 : gorc->paid ? "paid" : "unpaid",
658 : gorc->wired ? "wired" : "unwired");
659 52 : gorc->phase++;
660 52 : return;
661 : }
662 12 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
663 12 : GNUNET_assert (! gorc->paid);
664 : /* No contract, only order, fetch from orders table */
665 12 : gorc->order_only = true;
666 : {
667 : struct TALER_MerchantPostDataHashP unused;
668 :
669 : /* We need the order for two cases: Either when the contract doesn't exist yet,
670 : * or when the order is claimed but unpaid, and we need the claim token. */
671 12 : qs = TMH_db->lookup_order (TMH_db->cls,
672 12 : hc->instance->settings.id,
673 12 : hc->infix,
674 : &gorc->claim_token,
675 : &unused,
676 : &gorc->contract_terms_json);
677 : }
678 12 : if (0 > qs)
679 : {
680 : /* single, read-only SQL statements should never cause
681 : serialization problems */
682 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
683 : /* Always report on hard error as well to enable diagnostics */
684 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
685 0 : phase_end (gorc,
686 : TALER_MHD_reply_with_error (gorc->sc.con,
687 : MHD_HTTP_INTERNAL_SERVER_ERROR,
688 : TALER_EC_GENERIC_DB_FETCH_FAILED,
689 : "order"));
690 0 : return;
691 : }
692 12 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
693 : {
694 0 : phase_end (gorc,
695 : TALER_MHD_reply_with_error (gorc->sc.con,
696 : MHD_HTTP_NOT_FOUND,
697 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
698 0 : hc->infix));
699 0 : return;
700 : }
701 12 : gorc->phase++;
702 : }
703 :
704 :
705 : /**
706 : * Obtain parse contract terms of the order. Extracts the fulfillment URL,
707 : * total amount, summary and timestamp from the contract terms!
708 : *
709 : * @param[in,out] gorc order context to update
710 : */
711 : static void
712 64 : phase_parse_contract (struct GetOrderRequestContext *gorc)
713 : {
714 64 : struct TMH_HandlerContext *hc = gorc->hc;
715 :
716 64 : if (NULL == gorc->contract_terms)
717 : {
718 62 : gorc->contract_terms = TALER_MERCHANT_contract_parse (
719 : gorc->contract_terms_json,
720 : true);
721 :
722 62 : if (NULL == gorc->contract_terms)
723 : {
724 0 : GNUNET_break (0);
725 0 : phase_end (gorc,
726 : TALER_MHD_reply_with_error (
727 : gorc->sc.con,
728 : MHD_HTTP_INTERNAL_SERVER_ERROR,
729 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
730 0 : hc->infix));
731 0 : return;
732 : }
733 : }
734 :
735 64 : switch (gorc->contract_terms->version)
736 : {
737 64 : case TALER_MERCHANT_CONTRACT_VERSION_0:
738 64 : gorc->contract_amount = gorc->contract_terms->details.v0.brutto;
739 64 : break;
740 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
741 0 : if (gorc->choice_index >= 0)
742 : {
743 0 : if (gorc->choice_index >=
744 0 : gorc->contract_terms->details.v1.choices_len)
745 : {
746 0 : GNUNET_break (0);
747 0 : phase_end (gorc,
748 : TALER_MHD_reply_with_error (
749 : gorc->sc.con, MHD_HTTP_INTERNAL_SERVER_ERROR,
750 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
751 : NULL));
752 0 : return;
753 : }
754 :
755 0 : gorc->contract_amount =
756 0 : gorc->contract_terms->details.v1.choices[gorc->choice_index].amount;
757 : }
758 : else
759 : {
760 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
761 : "choice index %i for order %s is invalid or not yet available",
762 : gorc->choice_index,
763 : gorc->contract_terms->order_id);
764 : }
765 0 : break;
766 0 : default:
767 : {
768 0 : GNUNET_break (0);
769 0 : phase_end (gorc,
770 : TALER_MHD_reply_with_error (
771 : gorc->sc.con,
772 : MHD_HTTP_INTERNAL_SERVER_ERROR,
773 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
774 : NULL));
775 0 : return;
776 : }
777 : }
778 :
779 116 : if (! gorc->order_only && GNUNET_OK !=
780 52 : TALER_JSON_contract_hash (gorc->contract_terms_json,
781 : &gorc->h_contract_terms))
782 : {
783 : {
784 0 : GNUNET_break (0);
785 0 : phase_end (gorc,
786 : TALER_MHD_reply_with_error (gorc->sc.con,
787 : MHD_HTTP_INTERNAL_SERVER_ERROR,
788 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
789 : NULL));
790 0 : return;
791 : }
792 : }
793 64 : GNUNET_assert (NULL != gorc->contract_terms_json);
794 64 : GNUNET_assert (NULL != gorc->contract_terms);
795 64 : gorc->phase++;
796 : }
797 :
798 :
799 : /**
800 : * Check payment status of the order.
801 : *
802 : * @param[in,out] gorc order context to update
803 : */
804 : static void
805 64 : phase_check_paid (struct GetOrderRequestContext *gorc)
806 : {
807 64 : struct TMH_HandlerContext *hc = gorc->hc;
808 :
809 64 : if (gorc->order_only)
810 : {
811 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
812 : "Order %s unclaimed, no need to lookup payment status\n",
813 : hc->infix);
814 12 : GNUNET_assert (! gorc->paid);
815 12 : GNUNET_assert (! gorc->wired);
816 12 : gorc->phase++;
817 12 : return;
818 : }
819 52 : if (NULL == gorc->session_id)
820 : {
821 42 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
822 : "No session ID, do not need to lookup session-ID specific payment status (%s/%s)\n",
823 : gorc->paid ? "paid" : "unpaid",
824 : gorc->wired ? "wired" : "unwired");
825 42 : gorc->phase++;
826 42 : return;
827 : }
828 10 : if (! gorc->paid_session_matches)
829 : {
830 8 : gorc->paid = false;
831 8 : gorc->wired = false;
832 : }
833 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
834 : "Order %s %s for session %s (%s)\n",
835 : hc->infix,
836 : gorc->paid ? "paid" : "unpaid",
837 : gorc->session_id,
838 : gorc->wired ? "wired" : "unwired");
839 10 : gorc->phase++;
840 : }
841 :
842 :
843 : /**
844 : * Check if re-purchase detection applies to the order.
845 : *
846 : * @param[in,out] gorc order context to update
847 : */
848 : static void
849 64 : phase_check_repurchase (struct GetOrderRequestContext *gorc)
850 : {
851 64 : struct TMH_HandlerContext *hc = gorc->hc;
852 64 : char *already_paid_order_id = NULL;
853 : enum GNUNET_DB_QueryStatus qs;
854 : char *taler_pay_uri;
855 : char *order_status_url;
856 : MHD_RESULT ret;
857 :
858 64 : if ( (gorc->paid) ||
859 28 : (NULL == gorc->contract_terms->fulfillment_url) ||
860 17 : (NULL == gorc->session_id) )
861 : {
862 : /* Repurchase cannot apply */
863 56 : gorc->phase++;
864 60 : return;
865 : }
866 8 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
867 : "Running re-purchase detection for %s/%s\n",
868 : gorc->session_id,
869 : gorc->contract_terms->fulfillment_url);
870 8 : qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
871 8 : hc->instance->settings.id,
872 8 : gorc->contract_terms->
873 : fulfillment_url,
874 : gorc->session_id,
875 : TALER_EXCHANGE_YNA_NO !=
876 8 : gorc->allow_refunded_for_repurchase,
877 : &already_paid_order_id);
878 8 : if (0 > qs)
879 : {
880 : /* single, read-only SQL statements should never cause
881 : serialization problems, and the entry should exist as per above */
882 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
883 0 : phase_end (gorc,
884 : TALER_MHD_reply_with_error (gorc->sc.con,
885 : MHD_HTTP_INTERNAL_SERVER_ERROR,
886 : TALER_EC_GENERIC_DB_FETCH_FAILED,
887 : "order by fulfillment"));
888 0 : return;
889 : }
890 8 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
891 : {
892 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
893 : "No already paid order for %s/%s\n",
894 : gorc->session_id,
895 : gorc->contract_terms->fulfillment_url);
896 4 : gorc->phase++;
897 4 : return;
898 : }
899 :
900 : /* User did pay for this order, but under a different session; ask wallet to
901 : switch order ID */
902 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
903 : "Found already paid order %s\n",
904 : already_paid_order_id);
905 4 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
906 4 : hc->infix,
907 : gorc->session_id,
908 4 : hc->instance->settings.id,
909 : &gorc->claim_token);
910 4 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
911 4 : hc->infix,
912 : gorc->session_id,
913 4 : hc->instance->settings.id,
914 : &gorc->claim_token,
915 : NULL);
916 4 : if ( (NULL == taler_pay_uri) ||
917 : (NULL == order_status_url) )
918 : {
919 0 : GNUNET_break_op (0);
920 0 : GNUNET_free (taler_pay_uri);
921 0 : GNUNET_free (order_status_url);
922 0 : phase_end (gorc,
923 : TALER_MHD_reply_with_error (gorc->sc.con,
924 : MHD_HTTP_BAD_REQUEST,
925 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
926 : "host"));
927 0 : return;
928 : }
929 4 : ret = TALER_MHD_REPLY_JSON_PACK (
930 : gorc->sc.con,
931 : MHD_HTTP_OK,
932 : GNUNET_JSON_pack_string ("taler_pay_uri",
933 : taler_pay_uri),
934 : GNUNET_JSON_pack_string ("order_status_url",
935 : order_status_url),
936 : GNUNET_JSON_pack_string ("order_status",
937 : "unpaid"),
938 : GNUNET_JSON_pack_string ("already_paid_order_id",
939 : already_paid_order_id),
940 : GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
941 : gorc->contract_terms->fulfillment_url),
942 : /* undefined for unpaid v1 contracts */
943 : GNUNET_JSON_pack_allow_null (
944 : TALER_JSON_pack_amount ("total_amount",
945 : &gorc->contract_amount)),
946 : GNUNET_JSON_pack_string ("summary",
947 : gorc->contract_terms->summary),
948 : GNUNET_JSON_pack_timestamp ("pay_deadline",
949 : gorc->contract_terms->pay_deadline),
950 : GNUNET_JSON_pack_timestamp ("creation_time",
951 : gorc->contract_terms->timestamp));
952 4 : GNUNET_free (order_status_url);
953 4 : GNUNET_free (taler_pay_uri);
954 4 : GNUNET_free (already_paid_order_id);
955 4 : phase_end (gorc,
956 : ret);
957 : }
958 :
959 :
960 : /**
961 : * Check if we should suspend until the order is paid.
962 : *
963 : * @param[in,out] gorc order context to update
964 : */
965 : static void
966 60 : phase_unpaid_finish (struct GetOrderRequestContext *gorc)
967 : {
968 60 : struct TMH_HandlerContext *hc = gorc->hc;
969 : char *taler_pay_uri;
970 : char *order_status_url;
971 : MHD_RESULT ret;
972 :
973 60 : if (gorc->paid)
974 : {
975 36 : gorc->phase++;
976 48 : return;
977 : }
978 : /* User never paid for this order, suspend waiting
979 : on payment or return details. */
980 24 : if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
981 : {
982 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
983 : "Suspending GET /private/orders/%s\n",
984 : hc->infix);
985 2 : GNUNET_CONTAINER_DLL_insert (gorc_head,
986 : gorc_tail,
987 : gorc);
988 2 : gorc->phase = GOP_SUSPENDED_ON_UNPAID;
989 2 : gorc->suspended = GNUNET_YES;
990 2 : MHD_suspend_connection (gorc->sc.con);
991 2 : return;
992 : }
993 22 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
994 22 : hc->infix,
995 : gorc->session_id,
996 22 : hc->instance->settings.id,
997 : &gorc->claim_token,
998 : NULL);
999 22 : if (! gorc->order_only)
1000 : {
1001 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1002 : "Order %s claimed but not paid yet\n",
1003 : hc->infix);
1004 10 : phase_end (gorc,
1005 10 : TALER_MHD_REPLY_JSON_PACK (
1006 : gorc->sc.con,
1007 : MHD_HTTP_OK,
1008 : GNUNET_JSON_pack_string ("order_status_url",
1009 : order_status_url),
1010 : GNUNET_JSON_pack_object_incref ("contract_terms",
1011 : gorc->contract_terms_json),
1012 : GNUNET_JSON_pack_string ("order_status",
1013 : "claimed")));
1014 10 : GNUNET_free (order_status_url);
1015 10 : return;
1016 : }
1017 12 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
1018 12 : hc->infix,
1019 : gorc->session_id,
1020 12 : hc->instance->settings.id,
1021 : &gorc->claim_token);
1022 12 : ret = TALER_MHD_REPLY_JSON_PACK (
1023 : gorc->sc.con,
1024 : MHD_HTTP_OK,
1025 : GNUNET_JSON_pack_string ("taler_pay_uri",
1026 : taler_pay_uri),
1027 : GNUNET_JSON_pack_string ("order_status_url",
1028 : order_status_url),
1029 : GNUNET_JSON_pack_string ("order_status",
1030 : "unpaid"),
1031 : /* undefined for unpaid v1 contracts */
1032 : GNUNET_JSON_pack_allow_null (
1033 : TALER_JSON_pack_amount ("total_amount",
1034 : &gorc->contract_amount)),
1035 : GNUNET_JSON_pack_string ("summary",
1036 : gorc->contract_terms->summary),
1037 : GNUNET_JSON_pack_timestamp ("creation_time",
1038 : gorc->contract_terms->timestamp));
1039 12 : GNUNET_free (taler_pay_uri);
1040 12 : GNUNET_free (order_status_url);
1041 12 : phase_end (gorc,
1042 : ret);
1043 :
1044 : }
1045 :
1046 :
1047 : /**
1048 : * Function called with information about a refund.
1049 : * It is responsible for summing up the refund amount.
1050 : *
1051 : * @param cls closure
1052 : * @param refund_serial unique serial number of the refund
1053 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
1054 : * @param coin_pub public coin from which the refund comes from
1055 : * @param exchange_url URL of the exchange that issued @a coin_pub
1056 : * @param rtransaction_id identificator of the refund
1057 : * @param reason human-readable explanation of the refund
1058 : * @param refund_amount refund amount which is being taken from @a coin_pub
1059 : * @param pending true if the this refund was not yet processed by the wallet/exchange
1060 : */
1061 : static void
1062 8 : process_refunds_cb (
1063 : void *cls,
1064 : uint64_t refund_serial,
1065 : struct GNUNET_TIME_Timestamp timestamp,
1066 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
1067 : const char *exchange_url,
1068 : uint64_t rtransaction_id,
1069 : const char *reason,
1070 : const struct TALER_Amount *refund_amount,
1071 : bool pending)
1072 : {
1073 8 : struct GetOrderRequestContext *gorc = cls;
1074 :
1075 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1076 : "Found refund %llu over %s for reason %s\n",
1077 : (unsigned long long) rtransaction_id,
1078 : TALER_amount2s (refund_amount),
1079 : reason);
1080 8 : GNUNET_assert (
1081 : 0 ==
1082 : json_array_append_new (
1083 : gorc->refund_details,
1084 : GNUNET_JSON_PACK (
1085 : TALER_JSON_pack_amount ("amount",
1086 : refund_amount),
1087 : GNUNET_JSON_pack_bool ("pending",
1088 : pending),
1089 : GNUNET_JSON_pack_timestamp ("timestamp",
1090 : timestamp),
1091 : GNUNET_JSON_pack_string ("reason",
1092 : reason))));
1093 : /* For refunded coins, we are not charged deposit fees, so subtract those
1094 : again */
1095 8 : for (struct TransferQuery *tq = gorc->tq_head;
1096 8 : NULL != tq;
1097 0 : tq = tq->next)
1098 : {
1099 0 : if (0 !=
1100 0 : strcmp (exchange_url,
1101 0 : tq->exchange_url))
1102 0 : continue;
1103 0 : if (0 !=
1104 0 : GNUNET_memcmp (&tq->coin_pub,
1105 : coin_pub))
1106 0 : continue;
1107 0 : if (GNUNET_OK !=
1108 0 : TALER_amount_cmp_currency (
1109 0 : &gorc->deposit_fees_total,
1110 0 : &tq->deposit_fee))
1111 : {
1112 0 : gorc->refund_currency_mismatch = true;
1113 0 : return;
1114 : }
1115 0 : GNUNET_assert (
1116 : 0 <=
1117 : TALER_amount_subtract (&gorc->deposit_fees_total,
1118 : &gorc->deposit_fees_total,
1119 : &tq->deposit_fee));
1120 : }
1121 8 : if (GNUNET_OK !=
1122 8 : TALER_amount_cmp_currency (
1123 8 : &gorc->refund_amount,
1124 : refund_amount))
1125 : {
1126 0 : gorc->refund_currency_mismatch = true;
1127 0 : return;
1128 : }
1129 8 : GNUNET_assert (0 <=
1130 : TALER_amount_add (&gorc->refund_amount,
1131 : &gorc->refund_amount,
1132 : refund_amount));
1133 8 : gorc->refunded = true;
1134 8 : gorc->refund_pending |= pending;
1135 : }
1136 :
1137 :
1138 : /**
1139 : * Check refund status for the order.
1140 : *
1141 : * @param[in,out] gorc order context to update
1142 : */
1143 : static void
1144 36 : phase_check_refunds (struct GetOrderRequestContext *gorc)
1145 : {
1146 36 : struct TMH_HandlerContext *hc = gorc->hc;
1147 : enum GNUNET_DB_QueryStatus qs;
1148 :
1149 36 : GNUNET_assert (! gorc->order_only);
1150 36 : GNUNET_assert (gorc->paid);
1151 :
1152 : /* Accumulate refunds, if any. */
1153 36 : GNUNET_assert (GNUNET_OK ==
1154 : TALER_amount_set_zero (gorc->contract_amount.currency,
1155 : &gorc->refund_amount));
1156 :
1157 36 : qs = TMH_db->lookup_refunds_detailed (
1158 36 : TMH_db->cls,
1159 36 : hc->instance->settings.id,
1160 36 : &gorc->h_contract_terms,
1161 : &process_refunds_cb,
1162 : gorc);
1163 36 : if (0 > qs)
1164 : {
1165 0 : GNUNET_break (0);
1166 0 : phase_end (gorc,
1167 : TALER_MHD_reply_with_error (
1168 : gorc->sc.con,
1169 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1170 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1171 : "detailed refunds"));
1172 0 : return;
1173 : }
1174 36 : if (gorc->refund_currency_mismatch)
1175 : {
1176 0 : GNUNET_break (0);
1177 0 : phase_end (gorc,
1178 : TALER_MHD_reply_with_error (
1179 : gorc->sc.con,
1180 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1181 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1182 : "refunds in different currency than original order price"));
1183 0 : return;
1184 : }
1185 36 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1186 : "Total refunds are %s\n",
1187 : TALER_amount2s (&gorc->refund_amount));
1188 36 : gorc->phase++;
1189 : }
1190 :
1191 :
1192 : /**
1193 : * Function called with each @a coin_pub that was deposited into the
1194 : * @a h_wire account of the merchant for the @a deposit_serial as part
1195 : * of the payment for the order identified by @a cls.
1196 : *
1197 : * Queries the exchange for the payment status associated with the
1198 : * given coin.
1199 : *
1200 : * @param cls a `struct GetOrderRequestContext`
1201 : * @param deposit_serial identifies the deposit operation
1202 : * @param exchange_url URL of the exchange that issued @a coin_pub
1203 : * @param h_wire hash of the merchant's wire account into which the deposit was made
1204 : * @param deposit_timestamp when was the deposit made
1205 : * @param amount_with_fee amount the exchange will deposit for this coin
1206 : * @param deposit_fee fee the exchange will charge for this coin
1207 : * @param coin_pub public key of the deposited coin
1208 : */
1209 : static void
1210 36 : deposit_cb (
1211 : void *cls,
1212 : uint64_t deposit_serial,
1213 : const char *exchange_url,
1214 : const struct TALER_MerchantWireHashP *h_wire,
1215 : struct GNUNET_TIME_Timestamp deposit_timestamp,
1216 : const struct TALER_Amount *amount_with_fee,
1217 : const struct TALER_Amount *deposit_fee,
1218 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
1219 : {
1220 36 : struct GetOrderRequestContext *gorc = cls;
1221 : struct TransferQuery *tq;
1222 :
1223 36 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1224 : "Checking deposit status for coin %s (over %s)\n",
1225 : TALER_B2S (coin_pub),
1226 : TALER_amount2s (amount_with_fee));
1227 : gorc->last_payment
1228 36 : = GNUNET_TIME_timestamp_max (gorc->last_payment,
1229 : deposit_timestamp);
1230 36 : tq = GNUNET_new (struct TransferQuery);
1231 36 : tq->gorc = gorc;
1232 36 : tq->exchange_url = GNUNET_strdup (exchange_url);
1233 36 : tq->deposit_serial = deposit_serial;
1234 36 : GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
1235 : gorc->tq_tail,
1236 : tq);
1237 36 : tq->coin_pub = *coin_pub;
1238 36 : tq->h_wire = *h_wire;
1239 36 : tq->amount_with_fee = *amount_with_fee;
1240 36 : tq->deposit_fee = *deposit_fee;
1241 36 : }
1242 :
1243 :
1244 : /**
1245 : * Check wire transfer status for the order at the exchange.
1246 : *
1247 : * @param[in,out] gorc order context to update
1248 : */
1249 : static void
1250 36 : phase_check_deposits (struct GetOrderRequestContext *gorc)
1251 : {
1252 36 : GNUNET_assert (! gorc->order_only);
1253 36 : GNUNET_assert (gorc->paid);
1254 :
1255 : /* amount must be always valid for paid orders */
1256 36 : GNUNET_assert (GNUNET_OK ==
1257 : TALER_amount_is_valid (&gorc->contract_amount));
1258 :
1259 36 : GNUNET_assert (GNUNET_OK ==
1260 : TALER_amount_set_zero (gorc->contract_amount.currency,
1261 : &gorc->deposits_total));
1262 36 : GNUNET_assert (GNUNET_OK ==
1263 : TALER_amount_set_zero (gorc->contract_amount.currency,
1264 : &gorc->deposit_fees_total));
1265 36 : TMH_db->lookup_deposits_by_order (TMH_db->cls,
1266 : gorc->order_serial,
1267 : &deposit_cb,
1268 : gorc);
1269 36 : gorc->phase++;
1270 36 : }
1271 :
1272 :
1273 : /**
1274 : * Function called with available wire details, to be added to
1275 : * the response.
1276 : *
1277 : * @param cls a `struct GetOrderRequestContext`
1278 : * @param wtid wire transfer subject of the wire transfer for the coin
1279 : * @param exchange_url base URL of the exchange that made the payment
1280 : * @param execution_time when was the payment made
1281 : * @param deposit_value contribution of the coin to the total wire transfer value
1282 : * @param deposit_fee deposit fee charged by the exchange for the coin
1283 : * @param transfer_confirmed did the merchant confirm that a wire transfer with
1284 : * @a wtid over the total amount happened?
1285 : */
1286 : static void
1287 14 : process_transfer_details (
1288 : void *cls,
1289 : const struct TALER_WireTransferIdentifierRawP *wtid,
1290 : const char *exchange_url,
1291 : struct GNUNET_TIME_Timestamp execution_time,
1292 : const struct TALER_Amount *deposit_value,
1293 : const struct TALER_Amount *deposit_fee,
1294 : bool transfer_confirmed)
1295 : {
1296 14 : struct GetOrderRequestContext *gorc = cls;
1297 14 : json_t *wire_details = gorc->wire_details;
1298 : struct TALER_Amount wired;
1299 :
1300 14 : if ( (GNUNET_OK !=
1301 14 : TALER_amount_cmp_currency (&gorc->deposits_total,
1302 14 : deposit_value)) ||
1303 : (GNUNET_OK !=
1304 14 : TALER_amount_cmp_currency (&gorc->deposit_fees_total,
1305 : deposit_fee)) )
1306 : {
1307 0 : GNUNET_break (0);
1308 0 : gorc->deposit_currency_mismatch = true;
1309 0 : return;
1310 : }
1311 :
1312 : /* Compute total amount *wired* */
1313 14 : GNUNET_assert (0 <=
1314 : TALER_amount_add (&gorc->deposits_total,
1315 : &gorc->deposits_total,
1316 : deposit_value));
1317 14 : GNUNET_assert (0 <=
1318 : TALER_amount_add (&gorc->deposit_fees_total,
1319 : &gorc->deposit_fees_total,
1320 : deposit_fee));
1321 14 : GNUNET_assert (0 <= TALER_amount_subtract (&wired,
1322 : deposit_value,
1323 : deposit_fee));
1324 14 : GNUNET_assert (0 ==
1325 : json_array_append_new (
1326 : wire_details,
1327 : GNUNET_JSON_PACK (
1328 : GNUNET_JSON_pack_data_auto ("wtid",
1329 : wtid),
1330 : GNUNET_JSON_pack_string ("exchange_url",
1331 : exchange_url),
1332 : TALER_JSON_pack_amount ("amount",
1333 : &wired),
1334 : GNUNET_JSON_pack_timestamp ("execution_time",
1335 : execution_time),
1336 : GNUNET_JSON_pack_bool ("confirmed",
1337 : transfer_confirmed))));
1338 : }
1339 :
1340 :
1341 : /**
1342 : * Check transfer status in local database.
1343 : *
1344 : * @param[in,out] gorc order context to update
1345 : */
1346 : static void
1347 36 : phase_check_local_transfers (struct GetOrderRequestContext *gorc)
1348 : {
1349 36 : struct TMH_HandlerContext *hc = gorc->hc;
1350 : enum GNUNET_DB_QueryStatus qs;
1351 :
1352 36 : GNUNET_assert (gorc->paid);
1353 36 : GNUNET_assert (! gorc->order_only);
1354 :
1355 36 : GNUNET_assert (GNUNET_OK ==
1356 : TALER_amount_set_zero (gorc->contract_amount.currency,
1357 : &gorc->deposits_total));
1358 36 : GNUNET_assert (GNUNET_OK ==
1359 : TALER_amount_set_zero (gorc->contract_amount.currency,
1360 : &gorc->deposit_fees_total));
1361 :
1362 36 : qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
1363 : gorc->order_serial,
1364 : &process_transfer_details,
1365 : gorc);
1366 36 : if (0 > qs)
1367 : {
1368 0 : GNUNET_break (0);
1369 0 : phase_end (gorc,
1370 : TALER_MHD_reply_with_error (gorc->sc.con,
1371 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1372 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1373 : "transfer details"));
1374 0 : return;
1375 : }
1376 36 : if (gorc->deposit_currency_mismatch)
1377 : {
1378 0 : GNUNET_break (0);
1379 0 : phase_end (gorc,
1380 : TALER_MHD_reply_with_error (gorc->sc.con,
1381 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1382 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1383 : "deposits in different currency than original order price"));
1384 0 : return;
1385 : }
1386 :
1387 36 : if (! gorc->wired)
1388 : {
1389 : /* we believe(d) the wire transfer did not happen yet, check if maybe
1390 : in light of new evidence it did */
1391 : struct TALER_Amount expect_total;
1392 :
1393 22 : if (0 >
1394 22 : TALER_amount_subtract (&expect_total,
1395 22 : &gorc->contract_amount,
1396 22 : &gorc->refund_amount))
1397 : {
1398 0 : GNUNET_break (0);
1399 0 : phase_end (gorc,
1400 : TALER_MHD_reply_with_error (
1401 : gorc->sc.con,
1402 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1403 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1404 : "refund exceeds contract value"));
1405 0 : return;
1406 : }
1407 22 : if (0 >
1408 22 : TALER_amount_subtract (&expect_total,
1409 : &expect_total,
1410 22 : &gorc->deposit_fees_total))
1411 : {
1412 0 : GNUNET_break (0);
1413 0 : phase_end (gorc,
1414 : TALER_MHD_reply_with_error (
1415 : gorc->sc.con,
1416 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1417 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1418 : "deposit fees exceed total minus refunds"));
1419 0 : return;
1420 : }
1421 22 : if (0 >=
1422 22 : TALER_amount_cmp (&expect_total,
1423 22 : &gorc->deposits_total))
1424 : {
1425 : /* expect_total <= gorc->deposits_total: good: we got the wire transfer */
1426 0 : gorc->wired = true;
1427 0 : qs = TMH_db->mark_order_wired (TMH_db->cls,
1428 : gorc->order_serial);
1429 0 : GNUNET_break (qs >= 0); /* just warn if transaction failed */
1430 0 : TMH_notify_order_change (hc->instance,
1431 : TMH_OSF_PAID
1432 : | TMH_OSF_WIRED,
1433 0 : gorc->contract_terms->timestamp,
1434 : gorc->order_serial);
1435 : }
1436 : }
1437 36 : gorc->phase++;
1438 : }
1439 :
1440 :
1441 : /**
1442 : * Generate final result for the status request.
1443 : *
1444 : * @param[in,out] gorc order context to update
1445 : */
1446 : static void
1447 36 : phase_reply_result (struct GetOrderRequestContext *gorc)
1448 : {
1449 36 : struct TMH_HandlerContext *hc = gorc->hc;
1450 : MHD_RESULT ret;
1451 : char *order_status_url;
1452 :
1453 36 : GNUNET_assert (gorc->paid);
1454 36 : GNUNET_assert (! gorc->order_only);
1455 :
1456 : {
1457 36 : struct TALER_PrivateContractHashP *h_contract = NULL;
1458 :
1459 : /* In a session-bound payment, allow the browser to check the order
1460 : * status page (e.g. to get a refund).
1461 : *
1462 : * Note that we don't allow this outside of session-based payment, as
1463 : * otherwise this becomes an oracle to convert order_id to h_contract.
1464 : */
1465 36 : if (NULL != gorc->session_id)
1466 2 : h_contract = &gorc->h_contract_terms;
1467 :
1468 : order_status_url =
1469 36 : TMH_make_order_status_url (gorc->sc.con,
1470 36 : hc->infix,
1471 : gorc->session_id,
1472 36 : hc->instance->settings.id,
1473 : &gorc->claim_token,
1474 : h_contract);
1475 : }
1476 36 : if (GNUNET_TIME_absolute_is_zero (gorc->last_payment.abs_time))
1477 : {
1478 0 : GNUNET_break (GNUNET_YES ==
1479 : TALER_amount_is_zero (&gorc->contract_amount));
1480 0 : gorc->last_payment = gorc->contract_terms->timestamp;
1481 : }
1482 36 : ret = TALER_MHD_REPLY_JSON_PACK (
1483 : gorc->sc.con,
1484 : MHD_HTTP_OK,
1485 : // Deprecated in protocol v6.
1486 : GNUNET_JSON_pack_array_steal ("wire_reports",
1487 : json_array ()),
1488 : GNUNET_JSON_pack_uint64 ("exchange_code",
1489 : gorc->exchange_ec),
1490 : GNUNET_JSON_pack_uint64 ("exchange_http_status",
1491 : gorc->exchange_hc),
1492 : /* legacy: */
1493 : GNUNET_JSON_pack_uint64 ("exchange_ec",
1494 : gorc->exchange_ec),
1495 : /* legacy: */
1496 : GNUNET_JSON_pack_uint64 ("exchange_hc",
1497 : gorc->exchange_hc),
1498 : TALER_JSON_pack_amount ("deposit_total",
1499 : &gorc->deposits_total),
1500 : GNUNET_JSON_pack_object_incref ("contract_terms",
1501 : gorc->contract_terms_json),
1502 : GNUNET_JSON_pack_string ("order_status",
1503 : "paid"),
1504 : GNUNET_JSON_pack_timestamp ("last_payment",
1505 : gorc->last_payment),
1506 : GNUNET_JSON_pack_bool ("refunded",
1507 : gorc->refunded),
1508 : GNUNET_JSON_pack_bool ("wired",
1509 : gorc->wired),
1510 : GNUNET_JSON_pack_bool ("refund_pending",
1511 : gorc->refund_pending),
1512 : GNUNET_JSON_pack_allow_null (
1513 : TALER_JSON_pack_amount ("refund_amount",
1514 : &gorc->refund_amount)),
1515 : GNUNET_JSON_pack_array_steal ("wire_details",
1516 : gorc->wire_details),
1517 : GNUNET_JSON_pack_array_steal ("refund_details",
1518 : gorc->refund_details),
1519 : GNUNET_JSON_pack_string ("order_status_url",
1520 : order_status_url),
1521 : (gorc->choice_index >= 0)
1522 : ? GNUNET_JSON_pack_int64 ("choice_index",
1523 : gorc->choice_index)
1524 : : GNUNET_JSON_pack_end_ ());
1525 36 : GNUNET_free (order_status_url);
1526 36 : gorc->wire_details = NULL;
1527 36 : gorc->refund_details = NULL;
1528 36 : phase_end (gorc,
1529 : ret);
1530 36 : }
1531 :
1532 :
1533 : /**
1534 : * End with error status in wire_hc and wire_ec.
1535 : *
1536 : * @param[in,out] gorc order context to update
1537 : */
1538 : static void
1539 0 : phase_error (struct GetOrderRequestContext *gorc)
1540 : {
1541 0 : GNUNET_assert (TALER_EC_NONE != gorc->wire_ec);
1542 0 : phase_end (gorc,
1543 : TALER_MHD_reply_with_error (gorc->sc.con,
1544 : gorc->wire_hc,
1545 : gorc->wire_ec,
1546 : NULL));
1547 0 : }
1548 :
1549 :
1550 : MHD_RESULT
1551 64 : TMH_private_get_orders_ID (
1552 : const struct TMH_RequestHandler *rh,
1553 : struct MHD_Connection *connection,
1554 : struct TMH_HandlerContext *hc)
1555 : {
1556 64 : struct GetOrderRequestContext *gorc = hc->ctx;
1557 :
1558 64 : if (NULL == gorc)
1559 : {
1560 : /* First time here, parse request and check order is known */
1561 62 : GNUNET_assert (NULL != hc->infix);
1562 62 : gorc = GNUNET_new (struct GetOrderRequestContext);
1563 62 : hc->cc = &gorc_cleanup;
1564 62 : hc->ctx = gorc;
1565 62 : gorc->sc.con = connection;
1566 62 : gorc->hc = hc;
1567 62 : gorc->wire_details = json_array ();
1568 62 : GNUNET_assert (NULL != gorc->wire_details);
1569 62 : gorc->refund_details = json_array ();
1570 62 : GNUNET_assert (NULL != gorc->refund_details);
1571 62 : gorc->session_id = MHD_lookup_connection_value (connection,
1572 : MHD_GET_ARGUMENT_KIND,
1573 : "session_id");
1574 62 : if (! (TALER_MHD_arg_to_yna (connection,
1575 : "allow_refunded_for_repurchase",
1576 : TALER_EXCHANGE_YNA_NO,
1577 : &gorc->allow_refunded_for_repurchase)) )
1578 0 : return TALER_MHD_reply_with_error (connection,
1579 : MHD_HTTP_BAD_REQUEST,
1580 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1581 : "allow_refunded_for_repurchase");
1582 62 : TALER_MHD_parse_request_timeout (connection,
1583 : &gorc->sc.long_poll_timeout);
1584 62 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1585 : "Starting GET /private/orders/%s processing with timeout %s\n",
1586 : hc->infix,
1587 : GNUNET_STRINGS_absolute_time_to_string (
1588 : gorc->sc.long_poll_timeout));
1589 : }
1590 64 : if (GNUNET_SYSERR == gorc->suspended)
1591 0 : return MHD_NO; /* we are in shutdown */
1592 : while (1)
1593 : {
1594 1108 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1595 : "Processing order %s in phase %d\n",
1596 : hc->infix,
1597 : (int) gorc->phase);
1598 586 : switch (gorc->phase)
1599 : {
1600 62 : case GOP_INIT:
1601 62 : phase_init (gorc);
1602 62 : break;
1603 64 : case GOP_FETCH_CONTRACT:
1604 64 : phase_fetch_contract (gorc);
1605 64 : break;
1606 64 : case GOP_PARSE_CONTRACT:
1607 64 : phase_parse_contract (gorc);
1608 64 : break;
1609 64 : case GOP_CHECK_PAID:
1610 64 : phase_check_paid (gorc);
1611 64 : break;
1612 64 : case GOP_CHECK_REPURCHASE:
1613 64 : phase_check_repurchase (gorc);
1614 64 : break;
1615 60 : case GOP_UNPAID_FINISH:
1616 60 : phase_unpaid_finish (gorc);
1617 60 : break;
1618 36 : case GOP_CHECK_REFUNDS:
1619 36 : phase_check_refunds (gorc);
1620 36 : break;
1621 36 : case GOP_CHECK_DEPOSITS:
1622 36 : phase_check_deposits (gorc);
1623 36 : break;
1624 36 : case GOP_CHECK_LOCAL_TRANSFERS:
1625 36 : phase_check_local_transfers (gorc);
1626 36 : break;
1627 36 : case GOP_REPLY_RESULT:
1628 36 : phase_reply_result (gorc);
1629 36 : break;
1630 0 : case GOP_ERROR:
1631 0 : phase_error (gorc);
1632 0 : break;
1633 2 : case GOP_SUSPENDED_ON_UNPAID:
1634 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1635 : "Suspending order request awaiting payment\n");
1636 2 : return MHD_YES;
1637 62 : case GOP_END_YES:
1638 62 : return MHD_YES;
1639 0 : case GOP_END_NO:
1640 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1641 : "Closing connection, no response generated\n");
1642 0 : return MHD_NO;
1643 : }
1644 : } /* end first-time per-request initialization */
1645 : }
|