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 64 : gorc_cleanup (void *cls)
475 : {
476 64 : struct GetOrderRequestContext *gorc = cls;
477 : struct TransferQuery *tq;
478 :
479 102 : while (NULL != (tq = gorc->tq_head))
480 : {
481 38 : GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
482 : gorc->tq_tail,
483 : tq);
484 38 : GNUNET_free (tq->exchange_url);
485 38 : GNUNET_free (tq);
486 : }
487 :
488 64 : if (NULL != gorc->contract_terms_json)
489 64 : json_decref (gorc->contract_terms_json);
490 64 : if (NULL != gorc->contract_terms)
491 : {
492 64 : TALER_MERCHANT_contract_free (gorc->contract_terms);
493 64 : gorc->contract_terms = NULL;
494 : }
495 64 : if (NULL != gorc->wire_details)
496 26 : json_decref (gorc->wire_details);
497 64 : if (NULL != gorc->refund_details)
498 26 : json_decref (gorc->refund_details);
499 64 : if (NULL != gorc->tt)
500 : {
501 0 : GNUNET_SCHEDULER_cancel (gorc->tt);
502 0 : gorc->tt = NULL;
503 : }
504 64 : if (NULL != gorc->eh)
505 : {
506 4 : TMH_db->event_listen_cancel (gorc->eh);
507 4 : gorc->eh = NULL;
508 : }
509 64 : if (NULL != gorc->session_eh)
510 : {
511 0 : TMH_db->event_listen_cancel (gorc->session_eh);
512 0 : gorc->session_eh = NULL;
513 : }
514 64 : GNUNET_free (gorc);
515 64 : }
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 64 : phase_end (struct GetOrderRequestContext *gorc,
527 : MHD_RESULT mret)
528 : {
529 64 : gorc->phase = (MHD_YES == mret)
530 : ? GOP_END_YES
531 64 : : GOP_END_NO;
532 64 : }
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 64 : phase_init (struct GetOrderRequestContext *gorc)
542 : {
543 64 : struct TMH_HandlerContext *hc = gorc->hc;
544 64 : struct TMH_OrderPayEventP pay_eh = {
545 64 : .header.size = htons (sizeof (pay_eh)),
546 64 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
547 64 : .merchant_pub = hc->instance->merchant_pub
548 : };
549 :
550 64 : if (! GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
551 : {
552 60 : gorc->phase++;
553 60 : 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 66 : phase_fetch_contract (struct GetOrderRequestContext *gorc)
605 : {
606 66 : struct TMH_HandlerContext *hc = gorc->hc;
607 : enum GNUNET_DB_QueryStatus qs;
608 :
609 66 : 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 66 : TMH_db->preflight (TMH_db->cls);
623 66 : qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
624 66 : hc->instance->settings.id,
625 66 : 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 66 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
635 : "lookup_contract_terms (%s) returned %d\n",
636 : hc->infix,
637 : (int) qs);
638 66 : 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 66 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
653 : {
654 54 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
655 : "Order %s is %s (%s) according to database, choice %d\n",
656 : hc->infix,
657 : gorc->paid ? "paid" : "unpaid",
658 : gorc->wired ? "wired" : "unwired",
659 : (int) gorc->choice_index);
660 54 : gorc->phase++;
661 54 : return;
662 : }
663 12 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
664 12 : GNUNET_assert (! gorc->paid);
665 : /* No contract, only order, fetch from orders table */
666 12 : gorc->order_only = true;
667 : {
668 : struct TALER_MerchantPostDataHashP unused;
669 :
670 : /* We need the order for two cases: Either when the contract doesn't exist yet,
671 : * or when the order is claimed but unpaid, and we need the claim token. */
672 12 : qs = TMH_db->lookup_order (TMH_db->cls,
673 12 : hc->instance->settings.id,
674 12 : hc->infix,
675 : &gorc->claim_token,
676 : &unused,
677 : &gorc->contract_terms_json);
678 : }
679 12 : if (0 > qs)
680 : {
681 : /* single, read-only SQL statements should never cause
682 : serialization problems */
683 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
684 : /* Always report on hard error as well to enable diagnostics */
685 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
686 0 : phase_end (gorc,
687 : TALER_MHD_reply_with_error (gorc->sc.con,
688 : MHD_HTTP_INTERNAL_SERVER_ERROR,
689 : TALER_EC_GENERIC_DB_FETCH_FAILED,
690 : "order"));
691 0 : return;
692 : }
693 12 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
694 : {
695 0 : phase_end (gorc,
696 : TALER_MHD_reply_with_error (gorc->sc.con,
697 : MHD_HTTP_NOT_FOUND,
698 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
699 0 : hc->infix));
700 0 : return;
701 : }
702 12 : gorc->phase++;
703 : }
704 :
705 :
706 : /**
707 : * Obtain parse contract terms of the order. Extracts the fulfillment URL,
708 : * total amount, summary and timestamp from the contract terms!
709 : *
710 : * @param[in,out] gorc order context to update
711 : */
712 : static void
713 66 : phase_parse_contract (struct GetOrderRequestContext *gorc)
714 : {
715 66 : struct TMH_HandlerContext *hc = gorc->hc;
716 :
717 66 : if (NULL == gorc->contract_terms)
718 : {
719 64 : gorc->contract_terms = TALER_MERCHANT_contract_parse (
720 : gorc->contract_terms_json,
721 : true);
722 :
723 64 : if (NULL == gorc->contract_terms)
724 : {
725 0 : GNUNET_break (0);
726 0 : phase_end (gorc,
727 : TALER_MHD_reply_with_error (
728 : gorc->sc.con,
729 : MHD_HTTP_INTERNAL_SERVER_ERROR,
730 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
731 0 : hc->infix));
732 0 : return;
733 : }
734 : }
735 :
736 66 : switch (gorc->contract_terms->version)
737 : {
738 66 : case TALER_MERCHANT_CONTRACT_VERSION_0:
739 66 : gorc->contract_amount = gorc->contract_terms->details.v0.brutto;
740 66 : break;
741 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
742 0 : if (gorc->choice_index >= 0)
743 : {
744 0 : if (gorc->choice_index >=
745 0 : gorc->contract_terms->details.v1.choices_len)
746 : {
747 0 : GNUNET_break (0);
748 0 : phase_end (gorc,
749 : TALER_MHD_reply_with_error (
750 : gorc->sc.con,
751 : MHD_HTTP_INTERNAL_SERVER_ERROR,
752 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
753 : NULL));
754 0 : return;
755 : }
756 :
757 0 : gorc->contract_amount =
758 0 : gorc->contract_terms->details.v1.choices[gorc->choice_index].amount;
759 : }
760 : else
761 : {
762 0 : GNUNET_break (gorc->order_only);
763 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
764 : "Choice index %i for order %s is invalid or not yet available",
765 : gorc->choice_index,
766 : gorc->contract_terms->order_id);
767 : }
768 0 : break;
769 0 : default:
770 : {
771 0 : GNUNET_break (0);
772 0 : phase_end (gorc,
773 : TALER_MHD_reply_with_error (
774 : gorc->sc.con,
775 : MHD_HTTP_INTERNAL_SERVER_ERROR,
776 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
777 : NULL));
778 0 : return;
779 : }
780 : }
781 :
782 120 : if ( (! gorc->order_only) &&
783 : (GNUNET_OK !=
784 54 : TALER_JSON_contract_hash (gorc->contract_terms_json,
785 : &gorc->h_contract_terms)) )
786 : {
787 0 : GNUNET_break (0);
788 0 : phase_end (gorc,
789 : TALER_MHD_reply_with_error (gorc->sc.con,
790 : MHD_HTTP_INTERNAL_SERVER_ERROR,
791 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
792 : NULL));
793 0 : return;
794 : }
795 66 : GNUNET_assert (NULL != gorc->contract_terms_json);
796 66 : GNUNET_assert (NULL != gorc->contract_terms);
797 66 : gorc->phase++;
798 : }
799 :
800 :
801 : /**
802 : * Check payment status of the order.
803 : *
804 : * @param[in,out] gorc order context to update
805 : */
806 : static void
807 66 : phase_check_paid (struct GetOrderRequestContext *gorc)
808 : {
809 66 : struct TMH_HandlerContext *hc = gorc->hc;
810 :
811 66 : if (gorc->order_only)
812 : {
813 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
814 : "Order %s unclaimed, no need to lookup payment status\n",
815 : hc->infix);
816 12 : GNUNET_assert (! gorc->paid);
817 12 : GNUNET_assert (! gorc->wired);
818 12 : gorc->phase++;
819 12 : return;
820 : }
821 54 : if (NULL == gorc->session_id)
822 : {
823 44 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
824 : "No session ID, do not need to lookup session-ID specific payment status (%s/%s)\n",
825 : gorc->paid ? "paid" : "unpaid",
826 : gorc->wired ? "wired" : "unwired");
827 44 : gorc->phase++;
828 44 : return;
829 : }
830 10 : if (! gorc->paid_session_matches)
831 : {
832 8 : gorc->paid = false;
833 8 : gorc->wired = false;
834 : }
835 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
836 : "Order %s %s for session %s (%s)\n",
837 : hc->infix,
838 : gorc->paid ? "paid" : "unpaid",
839 : gorc->session_id,
840 : gorc->wired ? "wired" : "unwired");
841 10 : gorc->phase++;
842 : }
843 :
844 :
845 : /**
846 : * Check if re-purchase detection applies to the order.
847 : *
848 : * @param[in,out] gorc order context to update
849 : */
850 : static void
851 66 : phase_check_repurchase (struct GetOrderRequestContext *gorc)
852 : {
853 66 : struct TMH_HandlerContext *hc = gorc->hc;
854 66 : char *already_paid_order_id = NULL;
855 : enum GNUNET_DB_QueryStatus qs;
856 : char *taler_pay_uri;
857 : char *order_status_url;
858 : MHD_RESULT ret;
859 :
860 66 : if ( (gorc->paid) ||
861 28 : (NULL == gorc->contract_terms->fulfillment_url) ||
862 17 : (NULL == gorc->session_id) )
863 : {
864 : /* Repurchase cannot apply */
865 58 : gorc->phase++;
866 62 : return;
867 : }
868 8 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
869 : "Running re-purchase detection for %s/%s\n",
870 : gorc->session_id,
871 : gorc->contract_terms->fulfillment_url);
872 8 : qs = TMH_db->lookup_order_by_fulfillment (
873 8 : TMH_db->cls,
874 8 : hc->instance->settings.id,
875 8 : gorc->contract_terms->fulfillment_url,
876 : gorc->session_id,
877 : TALER_EXCHANGE_YNA_NO !=
878 8 : gorc->allow_refunded_for_repurchase,
879 : &already_paid_order_id);
880 8 : if (0 > qs)
881 : {
882 : /* single, read-only SQL statements should never cause
883 : serialization problems, and the entry should exist as per above */
884 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
885 0 : phase_end (gorc,
886 : TALER_MHD_reply_with_error (gorc->sc.con,
887 : MHD_HTTP_INTERNAL_SERVER_ERROR,
888 : TALER_EC_GENERIC_DB_FETCH_FAILED,
889 : "order by fulfillment"));
890 0 : return;
891 : }
892 8 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
893 : {
894 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
895 : "No already paid order for %s/%s\n",
896 : gorc->session_id,
897 : gorc->contract_terms->fulfillment_url);
898 4 : gorc->phase++;
899 4 : return;
900 : }
901 :
902 : /* User did pay for this order, but under a different session; ask wallet to
903 : switch order ID */
904 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
905 : "Found already paid order %s\n",
906 : already_paid_order_id);
907 4 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
908 4 : hc->infix,
909 : gorc->session_id,
910 4 : hc->instance->settings.id,
911 : &gorc->claim_token);
912 4 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
913 4 : hc->infix,
914 : gorc->session_id,
915 4 : hc->instance->settings.id,
916 : &gorc->claim_token,
917 : NULL);
918 4 : if ( (NULL == taler_pay_uri) ||
919 : (NULL == order_status_url) )
920 : {
921 0 : GNUNET_break_op (0);
922 0 : GNUNET_free (taler_pay_uri);
923 0 : GNUNET_free (order_status_url);
924 0 : phase_end (gorc,
925 : TALER_MHD_reply_with_error (gorc->sc.con,
926 : MHD_HTTP_BAD_REQUEST,
927 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
928 : "host"));
929 0 : return;
930 : }
931 4 : ret = TALER_MHD_REPLY_JSON_PACK (
932 : gorc->sc.con,
933 : MHD_HTTP_OK,
934 : GNUNET_JSON_pack_string ("taler_pay_uri",
935 : taler_pay_uri),
936 : GNUNET_JSON_pack_string ("order_status_url",
937 : order_status_url),
938 : GNUNET_JSON_pack_string ("order_status",
939 : "unpaid"),
940 : GNUNET_JSON_pack_string ("already_paid_order_id",
941 : already_paid_order_id),
942 : GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
943 : gorc->contract_terms->fulfillment_url),
944 : /* undefined for unpaid v1 contracts */
945 : GNUNET_JSON_pack_allow_null (
946 : TALER_JSON_pack_amount ("total_amount",
947 : TALER_amount_is_valid (&gorc->contract_amount)
948 : ? &gorc->contract_amount
949 : : NULL)),
950 : GNUNET_JSON_pack_string ("summary",
951 : gorc->contract_terms->summary),
952 : GNUNET_JSON_pack_timestamp ("pay_deadline",
953 : gorc->contract_terms->pay_deadline),
954 : GNUNET_JSON_pack_timestamp ("creation_time",
955 : gorc->contract_terms->timestamp));
956 4 : GNUNET_free (order_status_url);
957 4 : GNUNET_free (taler_pay_uri);
958 4 : GNUNET_free (already_paid_order_id);
959 4 : phase_end (gorc,
960 : ret);
961 : }
962 :
963 :
964 : /**
965 : * Check if we should suspend until the order is paid.
966 : *
967 : * @param[in,out] gorc order context to update
968 : */
969 : static void
970 62 : phase_unpaid_finish (struct GetOrderRequestContext *gorc)
971 : {
972 62 : struct TMH_HandlerContext *hc = gorc->hc;
973 : char *taler_pay_uri;
974 : char *order_status_url;
975 : MHD_RESULT ret;
976 :
977 62 : if (gorc->paid)
978 : {
979 38 : gorc->phase++;
980 50 : return;
981 : }
982 : /* User never paid for this order, suspend waiting
983 : on payment or return details. */
984 24 : if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
985 : {
986 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
987 : "Suspending GET /private/orders/%s\n",
988 : hc->infix);
989 2 : GNUNET_CONTAINER_DLL_insert (gorc_head,
990 : gorc_tail,
991 : gorc);
992 2 : gorc->phase = GOP_SUSPENDED_ON_UNPAID;
993 2 : gorc->suspended = GNUNET_YES;
994 2 : MHD_suspend_connection (gorc->sc.con);
995 2 : return;
996 : }
997 22 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
998 22 : hc->infix,
999 : gorc->session_id,
1000 22 : hc->instance->settings.id,
1001 : &gorc->claim_token,
1002 : NULL);
1003 22 : if (! gorc->order_only)
1004 : {
1005 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1006 : "Order %s claimed but not paid yet\n",
1007 : hc->infix);
1008 10 : phase_end (gorc,
1009 10 : TALER_MHD_REPLY_JSON_PACK (
1010 : gorc->sc.con,
1011 : MHD_HTTP_OK,
1012 : GNUNET_JSON_pack_string ("order_status_url",
1013 : order_status_url),
1014 : GNUNET_JSON_pack_object_incref ("contract_terms",
1015 : gorc->contract_terms_json),
1016 : GNUNET_JSON_pack_string ("order_status",
1017 : "claimed")));
1018 10 : GNUNET_free (order_status_url);
1019 10 : return;
1020 : }
1021 12 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
1022 12 : hc->infix,
1023 : gorc->session_id,
1024 12 : hc->instance->settings.id,
1025 : &gorc->claim_token);
1026 12 : ret = TALER_MHD_REPLY_JSON_PACK (
1027 : gorc->sc.con,
1028 : MHD_HTTP_OK,
1029 : GNUNET_JSON_pack_string ("taler_pay_uri",
1030 : taler_pay_uri),
1031 : GNUNET_JSON_pack_string ("order_status_url",
1032 : order_status_url),
1033 : GNUNET_JSON_pack_string ("order_status",
1034 : "unpaid"),
1035 : /* undefined for unpaid v1 contracts */
1036 : GNUNET_JSON_pack_allow_null (
1037 : TALER_JSON_pack_amount ("total_amount",
1038 : &gorc->contract_amount)),
1039 : GNUNET_JSON_pack_string ("summary",
1040 : gorc->contract_terms->summary),
1041 : GNUNET_JSON_pack_timestamp ("creation_time",
1042 : gorc->contract_terms->timestamp));
1043 12 : GNUNET_free (taler_pay_uri);
1044 12 : GNUNET_free (order_status_url);
1045 12 : phase_end (gorc,
1046 : ret);
1047 :
1048 : }
1049 :
1050 :
1051 : /**
1052 : * Function called with information about a refund.
1053 : * It is responsible for summing up the refund amount.
1054 : *
1055 : * @param cls closure
1056 : * @param refund_serial unique serial number of the refund
1057 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
1058 : * @param coin_pub public coin from which the refund comes from
1059 : * @param exchange_url URL of the exchange that issued @a coin_pub
1060 : * @param rtransaction_id identificator of the refund
1061 : * @param reason human-readable explanation of the refund
1062 : * @param refund_amount refund amount which is being taken from @a coin_pub
1063 : * @param pending true if the this refund was not yet processed by the wallet/exchange
1064 : */
1065 : static void
1066 8 : process_refunds_cb (
1067 : void *cls,
1068 : uint64_t refund_serial,
1069 : struct GNUNET_TIME_Timestamp timestamp,
1070 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
1071 : const char *exchange_url,
1072 : uint64_t rtransaction_id,
1073 : const char *reason,
1074 : const struct TALER_Amount *refund_amount,
1075 : bool pending)
1076 : {
1077 8 : struct GetOrderRequestContext *gorc = cls;
1078 :
1079 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1080 : "Found refund %llu over %s for reason %s\n",
1081 : (unsigned long long) rtransaction_id,
1082 : TALER_amount2s (refund_amount),
1083 : reason);
1084 8 : GNUNET_assert (
1085 : 0 ==
1086 : json_array_append_new (
1087 : gorc->refund_details,
1088 : GNUNET_JSON_PACK (
1089 : TALER_JSON_pack_amount ("amount",
1090 : refund_amount),
1091 : GNUNET_JSON_pack_bool ("pending",
1092 : pending),
1093 : GNUNET_JSON_pack_timestamp ("timestamp",
1094 : timestamp),
1095 : GNUNET_JSON_pack_string ("reason",
1096 : reason))));
1097 : /* For refunded coins, we are not charged deposit fees, so subtract those
1098 : again */
1099 8 : for (struct TransferQuery *tq = gorc->tq_head;
1100 8 : NULL != tq;
1101 0 : tq = tq->next)
1102 : {
1103 0 : if (0 !=
1104 0 : strcmp (exchange_url,
1105 0 : tq->exchange_url))
1106 0 : continue;
1107 0 : if (0 !=
1108 0 : GNUNET_memcmp (&tq->coin_pub,
1109 : coin_pub))
1110 0 : continue;
1111 0 : if (GNUNET_OK !=
1112 0 : TALER_amount_cmp_currency (
1113 0 : &gorc->deposit_fees_total,
1114 0 : &tq->deposit_fee))
1115 : {
1116 0 : gorc->refund_currency_mismatch = true;
1117 0 : return;
1118 : }
1119 0 : GNUNET_assert (
1120 : 0 <=
1121 : TALER_amount_subtract (&gorc->deposit_fees_total,
1122 : &gorc->deposit_fees_total,
1123 : &tq->deposit_fee));
1124 : }
1125 8 : if (GNUNET_OK !=
1126 8 : TALER_amount_cmp_currency (
1127 8 : &gorc->refund_amount,
1128 : refund_amount))
1129 : {
1130 0 : gorc->refund_currency_mismatch = true;
1131 0 : return;
1132 : }
1133 8 : GNUNET_assert (0 <=
1134 : TALER_amount_add (&gorc->refund_amount,
1135 : &gorc->refund_amount,
1136 : refund_amount));
1137 8 : gorc->refunded = true;
1138 8 : gorc->refund_pending |= pending;
1139 : }
1140 :
1141 :
1142 : /**
1143 : * Check refund status for the order.
1144 : *
1145 : * @param[in,out] gorc order context to update
1146 : */
1147 : static void
1148 38 : phase_check_refunds (struct GetOrderRequestContext *gorc)
1149 : {
1150 38 : struct TMH_HandlerContext *hc = gorc->hc;
1151 : enum GNUNET_DB_QueryStatus qs;
1152 :
1153 38 : GNUNET_assert (! gorc->order_only);
1154 38 : GNUNET_assert (gorc->paid);
1155 :
1156 : /* Accumulate refunds, if any. */
1157 38 : GNUNET_assert (GNUNET_OK ==
1158 : TALER_amount_set_zero (gorc->contract_amount.currency,
1159 : &gorc->refund_amount));
1160 :
1161 38 : qs = TMH_db->lookup_refunds_detailed (
1162 38 : TMH_db->cls,
1163 38 : hc->instance->settings.id,
1164 38 : &gorc->h_contract_terms,
1165 : &process_refunds_cb,
1166 : gorc);
1167 38 : if (0 > qs)
1168 : {
1169 0 : GNUNET_break (0);
1170 0 : phase_end (gorc,
1171 : TALER_MHD_reply_with_error (
1172 : gorc->sc.con,
1173 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1174 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1175 : "detailed refunds"));
1176 0 : return;
1177 : }
1178 38 : if (gorc->refund_currency_mismatch)
1179 : {
1180 0 : GNUNET_break (0);
1181 0 : phase_end (gorc,
1182 : TALER_MHD_reply_with_error (
1183 : gorc->sc.con,
1184 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1185 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1186 : "refunds in different currency than original order price"));
1187 0 : return;
1188 : }
1189 38 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1190 : "Total refunds are %s\n",
1191 : TALER_amount2s (&gorc->refund_amount));
1192 38 : gorc->phase++;
1193 : }
1194 :
1195 :
1196 : /**
1197 : * Function called with each @a coin_pub that was deposited into the
1198 : * @a h_wire account of the merchant for the @a deposit_serial as part
1199 : * of the payment for the order identified by @a cls.
1200 : *
1201 : * Queries the exchange for the payment status associated with the
1202 : * given coin.
1203 : *
1204 : * @param cls a `struct GetOrderRequestContext`
1205 : * @param deposit_serial identifies the deposit operation
1206 : * @param exchange_url URL of the exchange that issued @a coin_pub
1207 : * @param h_wire hash of the merchant's wire account into which the deposit was made
1208 : * @param deposit_timestamp when was the deposit made
1209 : * @param amount_with_fee amount the exchange will deposit for this coin
1210 : * @param deposit_fee fee the exchange will charge for this coin
1211 : * @param coin_pub public key of the deposited coin
1212 : */
1213 : static void
1214 38 : deposit_cb (
1215 : void *cls,
1216 : uint64_t deposit_serial,
1217 : const char *exchange_url,
1218 : const struct TALER_MerchantWireHashP *h_wire,
1219 : struct GNUNET_TIME_Timestamp deposit_timestamp,
1220 : const struct TALER_Amount *amount_with_fee,
1221 : const struct TALER_Amount *deposit_fee,
1222 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
1223 : {
1224 38 : struct GetOrderRequestContext *gorc = cls;
1225 : struct TransferQuery *tq;
1226 :
1227 38 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1228 : "Checking deposit status for coin %s (over %s)\n",
1229 : TALER_B2S (coin_pub),
1230 : TALER_amount2s (amount_with_fee));
1231 : gorc->last_payment
1232 38 : = GNUNET_TIME_timestamp_max (gorc->last_payment,
1233 : deposit_timestamp);
1234 38 : tq = GNUNET_new (struct TransferQuery);
1235 38 : tq->gorc = gorc;
1236 38 : tq->exchange_url = GNUNET_strdup (exchange_url);
1237 38 : tq->deposit_serial = deposit_serial;
1238 38 : GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
1239 : gorc->tq_tail,
1240 : tq);
1241 38 : tq->coin_pub = *coin_pub;
1242 38 : tq->h_wire = *h_wire;
1243 38 : tq->amount_with_fee = *amount_with_fee;
1244 38 : tq->deposit_fee = *deposit_fee;
1245 38 : }
1246 :
1247 :
1248 : /**
1249 : * Check wire transfer status for the order at the exchange.
1250 : *
1251 : * @param[in,out] gorc order context to update
1252 : */
1253 : static void
1254 38 : phase_check_deposits (struct GetOrderRequestContext *gorc)
1255 : {
1256 38 : GNUNET_assert (! gorc->order_only);
1257 38 : GNUNET_assert (gorc->paid);
1258 :
1259 : /* amount must be always valid for paid orders */
1260 38 : GNUNET_assert (GNUNET_OK ==
1261 : TALER_amount_is_valid (&gorc->contract_amount));
1262 :
1263 38 : GNUNET_assert (GNUNET_OK ==
1264 : TALER_amount_set_zero (gorc->contract_amount.currency,
1265 : &gorc->deposits_total));
1266 38 : GNUNET_assert (GNUNET_OK ==
1267 : TALER_amount_set_zero (gorc->contract_amount.currency,
1268 : &gorc->deposit_fees_total));
1269 38 : TMH_db->lookup_deposits_by_order (TMH_db->cls,
1270 : gorc->order_serial,
1271 : &deposit_cb,
1272 : gorc);
1273 38 : gorc->phase++;
1274 38 : }
1275 :
1276 :
1277 : /**
1278 : * Function called with available wire details, to be added to
1279 : * the response.
1280 : *
1281 : * @param cls a `struct GetOrderRequestContext`
1282 : * @param wtid wire transfer subject of the wire transfer for the coin
1283 : * @param exchange_url base URL of the exchange that made the payment
1284 : * @param execution_time when was the payment made
1285 : * @param deposit_value contribution of the coin to the total wire transfer value
1286 : * @param deposit_fee deposit fee charged by the exchange for the coin
1287 : * @param transfer_confirmed did the merchant confirm that a wire transfer with
1288 : * @a wtid over the total amount happened?
1289 : */
1290 : static void
1291 14 : process_transfer_details (
1292 : void *cls,
1293 : const struct TALER_WireTransferIdentifierRawP *wtid,
1294 : const char *exchange_url,
1295 : struct GNUNET_TIME_Timestamp execution_time,
1296 : const struct TALER_Amount *deposit_value,
1297 : const struct TALER_Amount *deposit_fee,
1298 : bool transfer_confirmed)
1299 : {
1300 14 : struct GetOrderRequestContext *gorc = cls;
1301 14 : json_t *wire_details = gorc->wire_details;
1302 : struct TALER_Amount wired;
1303 :
1304 14 : if ( (GNUNET_OK !=
1305 14 : TALER_amount_cmp_currency (&gorc->deposits_total,
1306 14 : deposit_value)) ||
1307 : (GNUNET_OK !=
1308 14 : TALER_amount_cmp_currency (&gorc->deposit_fees_total,
1309 : deposit_fee)) )
1310 : {
1311 0 : GNUNET_break (0);
1312 0 : gorc->deposit_currency_mismatch = true;
1313 0 : return;
1314 : }
1315 :
1316 : /* Compute total amount *wired* */
1317 14 : GNUNET_assert (0 <=
1318 : TALER_amount_add (&gorc->deposits_total,
1319 : &gorc->deposits_total,
1320 : deposit_value));
1321 14 : GNUNET_assert (0 <=
1322 : TALER_amount_add (&gorc->deposit_fees_total,
1323 : &gorc->deposit_fees_total,
1324 : deposit_fee));
1325 14 : GNUNET_assert (0 <= TALER_amount_subtract (&wired,
1326 : deposit_value,
1327 : deposit_fee));
1328 14 : GNUNET_assert (0 ==
1329 : json_array_append_new (
1330 : wire_details,
1331 : GNUNET_JSON_PACK (
1332 : GNUNET_JSON_pack_data_auto ("wtid",
1333 : wtid),
1334 : GNUNET_JSON_pack_string ("exchange_url",
1335 : exchange_url),
1336 : TALER_JSON_pack_amount ("amount",
1337 : &wired),
1338 : GNUNET_JSON_pack_timestamp ("execution_time",
1339 : execution_time),
1340 : GNUNET_JSON_pack_bool ("confirmed",
1341 : transfer_confirmed))));
1342 : }
1343 :
1344 :
1345 : /**
1346 : * Check transfer status in local database.
1347 : *
1348 : * @param[in,out] gorc order context to update
1349 : */
1350 : static void
1351 38 : phase_check_local_transfers (struct GetOrderRequestContext *gorc)
1352 : {
1353 38 : struct TMH_HandlerContext *hc = gorc->hc;
1354 : enum GNUNET_DB_QueryStatus qs;
1355 :
1356 38 : GNUNET_assert (gorc->paid);
1357 38 : GNUNET_assert (! gorc->order_only);
1358 :
1359 38 : GNUNET_assert (GNUNET_OK ==
1360 : TALER_amount_set_zero (gorc->contract_amount.currency,
1361 : &gorc->deposits_total));
1362 38 : GNUNET_assert (GNUNET_OK ==
1363 : TALER_amount_set_zero (gorc->contract_amount.currency,
1364 : &gorc->deposit_fees_total));
1365 :
1366 38 : qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
1367 : gorc->order_serial,
1368 : &process_transfer_details,
1369 : gorc);
1370 38 : if (0 > qs)
1371 : {
1372 0 : GNUNET_break (0);
1373 0 : phase_end (gorc,
1374 : TALER_MHD_reply_with_error (gorc->sc.con,
1375 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1376 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1377 : "transfer details"));
1378 0 : return;
1379 : }
1380 38 : if (gorc->deposit_currency_mismatch)
1381 : {
1382 0 : GNUNET_break (0);
1383 0 : phase_end (gorc,
1384 : TALER_MHD_reply_with_error (gorc->sc.con,
1385 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1386 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1387 : "deposits in different currency than original order price"));
1388 0 : return;
1389 : }
1390 :
1391 38 : if (! gorc->wired)
1392 : {
1393 : /* we believe(d) the wire transfer did not happen yet, check if maybe
1394 : in light of new evidence it did */
1395 : struct TALER_Amount expect_total;
1396 :
1397 24 : if (0 >
1398 24 : TALER_amount_subtract (&expect_total,
1399 24 : &gorc->contract_amount,
1400 24 : &gorc->refund_amount))
1401 : {
1402 0 : GNUNET_break (0);
1403 0 : phase_end (gorc,
1404 : TALER_MHD_reply_with_error (
1405 : gorc->sc.con,
1406 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1407 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1408 : "refund exceeds contract value"));
1409 0 : return;
1410 : }
1411 24 : if (0 >
1412 24 : TALER_amount_subtract (&expect_total,
1413 : &expect_total,
1414 24 : &gorc->deposit_fees_total))
1415 : {
1416 0 : GNUNET_break (0);
1417 0 : phase_end (gorc,
1418 : TALER_MHD_reply_with_error (
1419 : gorc->sc.con,
1420 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1421 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1422 : "deposit fees exceed total minus refunds"));
1423 0 : return;
1424 : }
1425 24 : if (0 >=
1426 24 : TALER_amount_cmp (&expect_total,
1427 24 : &gorc->deposits_total))
1428 : {
1429 : /* expect_total <= gorc->deposits_total: good: we got the wire transfer */
1430 0 : gorc->wired = true;
1431 0 : qs = TMH_db->mark_order_wired (TMH_db->cls,
1432 : gorc->order_serial);
1433 0 : GNUNET_break (qs >= 0); /* just warn if transaction failed */
1434 0 : TMH_notify_order_change (hc->instance,
1435 : TMH_OSF_PAID
1436 : | TMH_OSF_WIRED,
1437 0 : gorc->contract_terms->timestamp,
1438 : gorc->order_serial);
1439 : }
1440 : }
1441 38 : gorc->phase++;
1442 : }
1443 :
1444 :
1445 : /**
1446 : * Generate final result for the status request.
1447 : *
1448 : * @param[in,out] gorc order context to update
1449 : */
1450 : static void
1451 38 : phase_reply_result (struct GetOrderRequestContext *gorc)
1452 : {
1453 38 : struct TMH_HandlerContext *hc = gorc->hc;
1454 : MHD_RESULT ret;
1455 : char *order_status_url;
1456 :
1457 38 : GNUNET_assert (gorc->paid);
1458 38 : GNUNET_assert (! gorc->order_only);
1459 :
1460 : {
1461 38 : struct TALER_PrivateContractHashP *h_contract = NULL;
1462 :
1463 : /* In a session-bound payment, allow the browser to check the order
1464 : * status page (e.g. to get a refund).
1465 : *
1466 : * Note that we don't allow this outside of session-based payment, as
1467 : * otherwise this becomes an oracle to convert order_id to h_contract.
1468 : */
1469 38 : if (NULL != gorc->session_id)
1470 2 : h_contract = &gorc->h_contract_terms;
1471 :
1472 : order_status_url =
1473 38 : TMH_make_order_status_url (gorc->sc.con,
1474 38 : hc->infix,
1475 : gorc->session_id,
1476 38 : hc->instance->settings.id,
1477 : &gorc->claim_token,
1478 : h_contract);
1479 : }
1480 38 : if (GNUNET_TIME_absolute_is_zero (gorc->last_payment.abs_time))
1481 : {
1482 0 : GNUNET_break (GNUNET_YES ==
1483 : TALER_amount_is_zero (&gorc->contract_amount));
1484 0 : gorc->last_payment = gorc->contract_terms->timestamp;
1485 : }
1486 38 : ret = TALER_MHD_REPLY_JSON_PACK (
1487 : gorc->sc.con,
1488 : MHD_HTTP_OK,
1489 : // Deprecated in protocol v6.
1490 : GNUNET_JSON_pack_array_steal ("wire_reports",
1491 : json_array ()),
1492 : GNUNET_JSON_pack_uint64 ("exchange_code",
1493 : gorc->exchange_ec),
1494 : GNUNET_JSON_pack_uint64 ("exchange_http_status",
1495 : gorc->exchange_hc),
1496 : /* legacy: */
1497 : GNUNET_JSON_pack_uint64 ("exchange_ec",
1498 : gorc->exchange_ec),
1499 : /* legacy: */
1500 : GNUNET_JSON_pack_uint64 ("exchange_hc",
1501 : gorc->exchange_hc),
1502 : TALER_JSON_pack_amount ("deposit_total",
1503 : &gorc->deposits_total),
1504 : GNUNET_JSON_pack_object_incref ("contract_terms",
1505 : gorc->contract_terms_json),
1506 : GNUNET_JSON_pack_string ("order_status",
1507 : "paid"),
1508 : GNUNET_JSON_pack_timestamp ("last_payment",
1509 : gorc->last_payment),
1510 : GNUNET_JSON_pack_bool ("refunded",
1511 : gorc->refunded),
1512 : GNUNET_JSON_pack_bool ("wired",
1513 : gorc->wired),
1514 : GNUNET_JSON_pack_bool ("refund_pending",
1515 : gorc->refund_pending),
1516 : GNUNET_JSON_pack_allow_null (
1517 : TALER_JSON_pack_amount ("refund_amount",
1518 : &gorc->refund_amount)),
1519 : GNUNET_JSON_pack_array_steal ("wire_details",
1520 : gorc->wire_details),
1521 : GNUNET_JSON_pack_array_steal ("refund_details",
1522 : gorc->refund_details),
1523 : GNUNET_JSON_pack_string ("order_status_url",
1524 : order_status_url),
1525 : (gorc->choice_index >= 0)
1526 : ? GNUNET_JSON_pack_int64 ("choice_index",
1527 : gorc->choice_index)
1528 : : GNUNET_JSON_pack_end_ ());
1529 38 : GNUNET_free (order_status_url);
1530 38 : gorc->wire_details = NULL;
1531 38 : gorc->refund_details = NULL;
1532 38 : phase_end (gorc,
1533 : ret);
1534 38 : }
1535 :
1536 :
1537 : /**
1538 : * End with error status in wire_hc and wire_ec.
1539 : *
1540 : * @param[in,out] gorc order context to update
1541 : */
1542 : static void
1543 0 : phase_error (struct GetOrderRequestContext *gorc)
1544 : {
1545 0 : GNUNET_assert (TALER_EC_NONE != gorc->wire_ec);
1546 0 : phase_end (gorc,
1547 : TALER_MHD_reply_with_error (gorc->sc.con,
1548 : gorc->wire_hc,
1549 : gorc->wire_ec,
1550 : NULL));
1551 0 : }
1552 :
1553 :
1554 : MHD_RESULT
1555 66 : TMH_private_get_orders_ID (
1556 : const struct TMH_RequestHandler *rh,
1557 : struct MHD_Connection *connection,
1558 : struct TMH_HandlerContext *hc)
1559 : {
1560 66 : struct GetOrderRequestContext *gorc = hc->ctx;
1561 :
1562 66 : if (NULL == gorc)
1563 : {
1564 : /* First time here, parse request and check order is known */
1565 64 : GNUNET_assert (NULL != hc->infix);
1566 64 : gorc = GNUNET_new (struct GetOrderRequestContext);
1567 64 : hc->cc = &gorc_cleanup;
1568 64 : hc->ctx = gorc;
1569 64 : gorc->sc.con = connection;
1570 64 : gorc->hc = hc;
1571 64 : gorc->wire_details = json_array ();
1572 64 : GNUNET_assert (NULL != gorc->wire_details);
1573 64 : gorc->refund_details = json_array ();
1574 64 : GNUNET_assert (NULL != gorc->refund_details);
1575 64 : gorc->session_id = MHD_lookup_connection_value (connection,
1576 : MHD_GET_ARGUMENT_KIND,
1577 : "session_id");
1578 64 : if (! (TALER_MHD_arg_to_yna (connection,
1579 : "allow_refunded_for_repurchase",
1580 : TALER_EXCHANGE_YNA_NO,
1581 : &gorc->allow_refunded_for_repurchase)) )
1582 0 : return TALER_MHD_reply_with_error (connection,
1583 : MHD_HTTP_BAD_REQUEST,
1584 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1585 : "allow_refunded_for_repurchase");
1586 64 : TALER_MHD_parse_request_timeout (connection,
1587 : &gorc->sc.long_poll_timeout);
1588 64 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1589 : "Starting GET /private/orders/%s processing with timeout %s\n",
1590 : hc->infix,
1591 : GNUNET_STRINGS_absolute_time_to_string (
1592 : gorc->sc.long_poll_timeout));
1593 : }
1594 66 : if (GNUNET_SYSERR == gorc->suspended)
1595 0 : return MHD_NO; /* we are in shutdown */
1596 : while (1)
1597 : {
1598 1150 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1599 : "Processing order %s in phase %d\n",
1600 : hc->infix,
1601 : (int) gorc->phase);
1602 608 : switch (gorc->phase)
1603 : {
1604 64 : case GOP_INIT:
1605 64 : phase_init (gorc);
1606 64 : break;
1607 66 : case GOP_FETCH_CONTRACT:
1608 66 : phase_fetch_contract (gorc);
1609 66 : break;
1610 66 : case GOP_PARSE_CONTRACT:
1611 66 : phase_parse_contract (gorc);
1612 66 : break;
1613 66 : case GOP_CHECK_PAID:
1614 66 : phase_check_paid (gorc);
1615 66 : break;
1616 66 : case GOP_CHECK_REPURCHASE:
1617 66 : phase_check_repurchase (gorc);
1618 66 : break;
1619 62 : case GOP_UNPAID_FINISH:
1620 62 : phase_unpaid_finish (gorc);
1621 62 : break;
1622 38 : case GOP_CHECK_REFUNDS:
1623 38 : phase_check_refunds (gorc);
1624 38 : break;
1625 38 : case GOP_CHECK_DEPOSITS:
1626 38 : phase_check_deposits (gorc);
1627 38 : break;
1628 38 : case GOP_CHECK_LOCAL_TRANSFERS:
1629 38 : phase_check_local_transfers (gorc);
1630 38 : break;
1631 38 : case GOP_REPLY_RESULT:
1632 38 : phase_reply_result (gorc);
1633 38 : break;
1634 0 : case GOP_ERROR:
1635 0 : phase_error (gorc);
1636 0 : break;
1637 2 : case GOP_SUSPENDED_ON_UNPAID:
1638 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1639 : "Suspending order request awaiting payment\n");
1640 2 : return MHD_YES;
1641 64 : case GOP_END_YES:
1642 64 : return MHD_YES;
1643 0 : case GOP_END_NO:
1644 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1645 : "Closing connection, no response generated\n");
1646 0 : return MHD_NO;
1647 : }
1648 : } /* end first-time per-request initialization */
1649 : }
|