Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Affero General Public License as
7 : published by the Free Software Foundation; either version 3,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file taler-merchant-httpd_post-orders-ID-abort.c
21 : * @brief handling of POST /orders/$ID/abort requests
22 : * @author Marcello Stanisci
23 : * @author Christian Grothoff
24 : * @author Florian Dold
25 : */
26 : #include "platform.h"
27 : #include <taler/taler_json_lib.h>
28 : #include <taler/taler_exchange_service.h>
29 : #include "taler-merchant-httpd_exchanges.h"
30 : #include "taler-merchant-httpd_helper.h"
31 : #include "taler-merchant-httpd_post-orders-ID-abort.h"
32 :
33 :
34 : /**
35 : * How long to wait before giving up processing with the exchange?
36 : */
37 : #define ABORT_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
38 : GNUNET_TIME_UNIT_SECONDS, \
39 : 30))
40 :
41 : /**
42 : * How often do we retry the (complex!) database transaction?
43 : */
44 : #define MAX_RETRIES 5
45 :
46 : /**
47 : * Information we keep for an individual call to the /abort handler.
48 : */
49 : struct AbortContext;
50 :
51 : /**
52 : * Information kept during a /abort request for each coin.
53 : */
54 : struct RefundDetails
55 : {
56 :
57 : /**
58 : * Public key of the coin.
59 : */
60 : struct TALER_CoinSpendPublicKeyP coin_pub;
61 :
62 : /**
63 : * Signature from the exchange confirming the refund.
64 : * Set if we were successful (status 200).
65 : */
66 : struct TALER_ExchangeSignatureP exchange_sig;
67 :
68 : /**
69 : * Public key used for @e exchange_sig.
70 : * Set if we were successful (status 200).
71 : */
72 : struct TALER_ExchangePublicKeyP exchange_pub;
73 :
74 : /**
75 : * Reference to the main AbortContext
76 : */
77 : struct AbortContext *ac;
78 :
79 : /**
80 : * Handle to the refund operation we are performing for
81 : * this coin, NULL after the operation is done.
82 : */
83 : struct TALER_EXCHANGE_RefundHandle *rh;
84 :
85 : /**
86 : * URL of the exchange that issued this coin.
87 : */
88 : char *exchange_url;
89 :
90 : /**
91 : * Body of the response from the exchange. Note that the body returned MUST
92 : * be freed (if non-NULL).
93 : */
94 : json_t *exchange_reply;
95 :
96 : /**
97 : * Amount this coin contributes to the total purchase price.
98 : * This amount includes the deposit fee.
99 : */
100 : struct TALER_Amount amount_with_fee;
101 :
102 : /**
103 : * Offset of this coin into the `rd` array of all coins in the
104 : * @e ac.
105 : */
106 : unsigned int index;
107 :
108 : /**
109 : * HTTP status returned by the exchange (if any).
110 : */
111 : unsigned int http_status;
112 :
113 : /**
114 : * Did we try to process this refund yet?
115 : */
116 : bool processed;
117 :
118 : /**
119 : * Did we find the deposit in our own database?
120 : */
121 : bool found_deposit;
122 : };
123 :
124 :
125 : /**
126 : * Information we keep for an individual call to the /abort handler.
127 : */
128 : struct AbortContext
129 : {
130 :
131 : /**
132 : * Hashed contract terms (according to client).
133 : */
134 : struct TALER_PrivateContractHashP h_contract_terms;
135 :
136 : /**
137 : * Context for our operation.
138 : */
139 : struct TMH_HandlerContext *hc;
140 :
141 : /**
142 : * Stored in a DLL.
143 : */
144 : struct AbortContext *next;
145 :
146 : /**
147 : * Stored in a DLL.
148 : */
149 : struct AbortContext *prev;
150 :
151 : /**
152 : * Array with @e coins_cnt coins we are despositing.
153 : */
154 : struct RefundDetails *rd;
155 :
156 : /**
157 : * MHD connection to return to
158 : */
159 : struct MHD_Connection *connection;
160 :
161 : /**
162 : * Task called when the (suspended) processing for
163 : * the /abort request times out.
164 : * Happens when we don't get a response from the exchange.
165 : */
166 : struct GNUNET_SCHEDULER_Task *timeout_task;
167 :
168 : /**
169 : * Response to return, NULL if we don't have one yet.
170 : */
171 : struct MHD_Response *response;
172 :
173 : /**
174 : * Handle to the exchange that we are doing the abortment with.
175 : * (initially NULL while @e fo is trying to find a exchange).
176 : */
177 : struct TALER_EXCHANGE_Handle *mh;
178 :
179 : /**
180 : * Handle for operation to lookup /keys (and auditors) from
181 : * the exchange used for this transaction; NULL if no operation is
182 : * pending.
183 : */
184 : struct TMH_EXCHANGES_KeysOperation *fo;
185 :
186 : /**
187 : * URL of the exchange used for the last @e fo.
188 : */
189 : const char *current_exchange;
190 :
191 : /**
192 : * Number of coins this abort is for. Length of the @e rd array.
193 : */
194 : size_t coins_cnt;
195 :
196 : /**
197 : * How often have we retried the 'main' transaction?
198 : */
199 : unsigned int retry_counter;
200 :
201 : /**
202 : * Number of transactions still pending. Initially set to
203 : * @e coins_cnt, decremented on each transaction that
204 : * successfully finished.
205 : */
206 : size_t pending;
207 :
208 : /**
209 : * Number of transactions still pending for the currently selected
210 : * exchange. Initially set to the number of coins started at the
211 : * exchange, decremented on each transaction that successfully
212 : * finished. Once it hits zero, we pick the next exchange.
213 : */
214 : size_t pending_at_ce;
215 :
216 : /**
217 : * HTTP status code to use for the reply, i.e 200 for "OK".
218 : * Special value UINT_MAX is used to indicate hard errors
219 : * (no reply, return #MHD_NO).
220 : */
221 : unsigned int response_code;
222 :
223 : /**
224 : * #GNUNET_NO if the @e connection was not suspended,
225 : * #GNUNET_YES if the @e connection was suspended,
226 : * #GNUNET_SYSERR if @e connection was resumed to as
227 : * part of #MH_force_ac_resume during shutdown.
228 : */
229 : int suspended;
230 :
231 : };
232 :
233 :
234 : /**
235 : * Head of active abort context DLL.
236 : */
237 : static struct AbortContext *ac_head;
238 :
239 : /**
240 : * Tail of active abort context DLL.
241 : */
242 : static struct AbortContext *ac_tail;
243 :
244 :
245 : /**
246 : * Abort all pending /deposit operations.
247 : *
248 : * @param ac abort context to abort
249 : */
250 : static void
251 4 : abort_refunds (struct AbortContext *ac)
252 : {
253 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
254 : "Aborting pending /deposit operations\n");
255 8 : for (size_t i = 0; i<ac->coins_cnt; i++)
256 : {
257 4 : struct RefundDetails *rdi = &ac->rd[i];
258 :
259 4 : if (NULL != rdi->rh)
260 : {
261 0 : TALER_EXCHANGE_refund_cancel (rdi->rh);
262 0 : rdi->rh = NULL;
263 : }
264 : }
265 4 : }
266 :
267 :
268 : void
269 14 : TMH_force_ac_resume ()
270 : {
271 14 : for (struct AbortContext *ac = ac_head;
272 14 : NULL != ac;
273 0 : ac = ac->next)
274 : {
275 0 : abort_refunds (ac);
276 0 : if (NULL != ac->timeout_task)
277 : {
278 0 : GNUNET_SCHEDULER_cancel (ac->timeout_task);
279 0 : ac->timeout_task = NULL;
280 : }
281 0 : if (GNUNET_YES == ac->suspended)
282 : {
283 0 : ac->suspended = GNUNET_SYSERR;
284 0 : MHD_resume_connection (ac->connection);
285 : }
286 : }
287 14 : }
288 :
289 :
290 : /**
291 : * Resume the given abort context and send the given response.
292 : * Stores the response in the @a ac and signals MHD to resume
293 : * the connection. Also ensures MHD runs immediately.
294 : *
295 : * @param ac abortment context
296 : * @param response_code response code to use
297 : * @param response response data to send back
298 : */
299 : static void
300 2 : resume_abort_with_response (struct AbortContext *ac,
301 : unsigned int response_code,
302 : struct MHD_Response *response)
303 : {
304 2 : abort_refunds (ac);
305 2 : ac->response_code = response_code;
306 2 : ac->response = response;
307 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
308 : "Resuming /abort handling as exchange interaction is done (%u)\n",
309 : response_code);
310 2 : if (NULL != ac->timeout_task)
311 : {
312 2 : GNUNET_SCHEDULER_cancel (ac->timeout_task);
313 2 : ac->timeout_task = NULL;
314 : }
315 2 : GNUNET_assert (GNUNET_YES == ac->suspended);
316 2 : ac->suspended = GNUNET_NO;
317 2 : MHD_resume_connection (ac->connection);
318 2 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
319 2 : }
320 :
321 :
322 : /**
323 : * Resume abortment processing with an error.
324 : *
325 : * @param ac operation to resume
326 : * @param http_status http status code to return
327 : * @param ec taler error code to return
328 : * @param msg human readable error message
329 : */
330 : static void
331 0 : resume_abort_with_error (struct AbortContext *ac,
332 : unsigned int http_status,
333 : enum TALER_ErrorCode ec,
334 : const char *msg)
335 : {
336 0 : resume_abort_with_response (ac,
337 : http_status,
338 : TALER_MHD_make_error (ec,
339 : msg));
340 0 : }
341 :
342 :
343 : /**
344 : * Generate a response that indicates abortment success.
345 : *
346 : * @param ac abortment context
347 : */
348 : static void
349 2 : generate_success_response (struct AbortContext *ac)
350 : {
351 : json_t *refunds;
352 2 : unsigned int hc = MHD_HTTP_OK;
353 :
354 2 : refunds = json_array ();
355 2 : if (NULL == refunds)
356 : {
357 0 : GNUNET_break (0);
358 0 : resume_abort_with_error (ac,
359 : MHD_HTTP_INTERNAL_SERVER_ERROR,
360 : TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
361 : "could not create JSON array");
362 0 : return;
363 : }
364 4 : for (size_t i = 0; i<ac->coins_cnt; i++)
365 : {
366 2 : struct RefundDetails *rdi = &ac->rd[i];
367 : json_t *detail;
368 :
369 2 : if (rdi->found_deposit)
370 : {
371 2 : if ( ( (MHD_HTTP_BAD_REQUEST <= rdi->http_status) &&
372 0 : (MHD_HTTP_NOT_FOUND != rdi->http_status) &&
373 0 : (MHD_HTTP_GONE != rdi->http_status) ) ||
374 2 : (0 == rdi->http_status) ||
375 2 : (NULL == rdi->exchange_reply) )
376 : {
377 0 : hc = MHD_HTTP_BAD_GATEWAY;
378 : }
379 : }
380 2 : if (! rdi->found_deposit)
381 : {
382 0 : detail = GNUNET_JSON_PACK (
383 : GNUNET_JSON_pack_string ("type",
384 : "undeposited"));
385 : }
386 : else
387 : {
388 2 : if (MHD_HTTP_OK != rdi->http_status)
389 : {
390 0 : detail = GNUNET_JSON_PACK (
391 : GNUNET_JSON_pack_string ("type",
392 : "failure"),
393 : GNUNET_JSON_pack_uint64 ("exchange_status",
394 : rdi->http_status),
395 : GNUNET_JSON_pack_uint64 ("exchange_code",
396 : (NULL != rdi->exchange_reply)
397 : ? TALER_JSON_get_error_code (
398 : rdi->exchange_reply)
399 : : TALER_EC_GENERIC_INVALID_RESPONSE),
400 : GNUNET_JSON_pack_allow_null (
401 : GNUNET_JSON_pack_object_incref ("exchange_reply",
402 : rdi->exchange_reply)));
403 : }
404 : else
405 : {
406 2 : detail = GNUNET_JSON_PACK (
407 : GNUNET_JSON_pack_string ("type",
408 : "success"),
409 : GNUNET_JSON_pack_uint64 ("exchange_status",
410 : rdi->http_status),
411 : GNUNET_JSON_pack_data_auto ("exchange_sig",
412 : &rdi->exchange_sig),
413 : GNUNET_JSON_pack_data_auto ("exchange_pub",
414 : &rdi->exchange_pub));
415 : }
416 : }
417 2 : GNUNET_assert (0 ==
418 : json_array_append_new (refunds,
419 : detail));
420 : }
421 :
422 : /* Resume and send back the response. */
423 2 : resume_abort_with_response (
424 : ac,
425 : hc,
426 2 : TALER_MHD_MAKE_JSON_PACK (
427 : GNUNET_JSON_pack_array_steal ("refunds",
428 : refunds)));
429 : }
430 :
431 :
432 : /**
433 : * Custom cleanup routine for a `struct AbortContext`.
434 : *
435 : * @param cls the `struct AbortContext` to clean up.
436 : */
437 : static void
438 2 : abort_context_cleanup (void *cls)
439 : {
440 2 : struct AbortContext *ac = cls;
441 :
442 2 : if (NULL != ac->timeout_task)
443 : {
444 0 : GNUNET_SCHEDULER_cancel (ac->timeout_task);
445 0 : ac->timeout_task = NULL;
446 : }
447 2 : abort_refunds (ac);
448 4 : for (size_t i = 0; i<ac->coins_cnt; i++)
449 : {
450 2 : struct RefundDetails *rdi = &ac->rd[i];
451 :
452 2 : if (NULL != rdi->exchange_reply)
453 : {
454 2 : json_decref (rdi->exchange_reply);
455 2 : rdi->exchange_reply = NULL;
456 : }
457 2 : GNUNET_free (rdi->exchange_url);
458 : }
459 2 : GNUNET_free (ac->rd);
460 2 : if (NULL != ac->fo)
461 : {
462 0 : TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
463 0 : ac->fo = NULL;
464 : }
465 2 : if (NULL != ac->response)
466 : {
467 0 : MHD_destroy_response (ac->response);
468 0 : ac->response = NULL;
469 : }
470 2 : GNUNET_CONTAINER_DLL_remove (ac_head,
471 : ac_tail,
472 : ac);
473 2 : GNUNET_free (ac);
474 2 : }
475 :
476 :
477 : /**
478 : * Find the exchange we need to talk to for the next
479 : * pending deposit permission.
480 : *
481 : * @param ac abortment context we are processing
482 : */
483 : static void
484 : find_next_exchange (struct AbortContext *ac);
485 :
486 :
487 : /**
488 : * Function called with the result from the exchange (to be
489 : * passed back to the wallet).
490 : *
491 : * @param cls closure
492 : * @param rr response data
493 : */
494 : static void
495 2 : refund_cb (void *cls,
496 : const struct TALER_EXCHANGE_RefundResponse *rr)
497 : {
498 2 : struct RefundDetails *rd = cls;
499 2 : const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
500 2 : struct AbortContext *ac = rd->ac;
501 :
502 2 : rd->rh = NULL;
503 2 : rd->http_status = hr->http_status;
504 2 : rd->exchange_reply = json_incref ((json_t*) hr->reply);
505 2 : if (MHD_HTTP_OK == hr->http_status)
506 : {
507 2 : rd->exchange_pub = rr->details.ok.exchange_pub;
508 2 : rd->exchange_sig = rr->details.ok.exchange_sig;
509 : }
510 2 : ac->pending_at_ce--;
511 2 : if (0 == ac->pending_at_ce)
512 2 : find_next_exchange (ac);
513 2 : }
514 :
515 :
516 : /**
517 : * Function called with the result of our exchange lookup.
518 : *
519 : * @param cls the `struct AbortContext`
520 : * @param keys keys of the exchange
521 : * @param exchange representation of the exchange
522 : */
523 : static void
524 2 : process_abort_with_exchange (void *cls,
525 : struct TALER_EXCHANGE_Keys *keys,
526 : struct TMH_Exchange *exchange)
527 : {
528 2 : struct AbortContext *ac = cls;
529 :
530 : (void) exchange;
531 2 : ac->fo = NULL;
532 2 : GNUNET_assert (GNUNET_YES == ac->suspended);
533 2 : if (NULL == keys)
534 : {
535 0 : resume_abort_with_response (
536 : ac,
537 : MHD_HTTP_GATEWAY_TIMEOUT,
538 : TALER_MHD_make_error (
539 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
540 : NULL));
541 0 : return;
542 : }
543 : /* Initiate refund operation for all coins of
544 : the current exchange (!) */
545 2 : GNUNET_assert (0 == ac->pending_at_ce);
546 4 : for (size_t i = 0; i<ac->coins_cnt; i++)
547 : {
548 2 : struct RefundDetails *rdi = &ac->rd[i];
549 :
550 2 : if (rdi->processed)
551 0 : continue;
552 2 : GNUNET_assert (NULL == rdi->rh);
553 2 : if (0 != strcmp (rdi->exchange_url,
554 : ac->current_exchange))
555 0 : continue;
556 2 : rdi->processed = true;
557 2 : ac->pending--;
558 2 : if (! rdi->found_deposit)
559 : {
560 : /* Coin wasn't even deposited yet, we do not need to refund it. */
561 0 : continue;
562 : }
563 4 : rdi->rh = TALER_EXCHANGE_refund (
564 : TMH_curl_ctx,
565 : ac->current_exchange,
566 : keys,
567 2 : &rdi->amount_with_fee,
568 2 : &ac->h_contract_terms,
569 2 : &rdi->coin_pub,
570 : 0, /* rtransaction_id */
571 2 : &ac->hc->instance->merchant_priv,
572 : &refund_cb,
573 : rdi);
574 2 : if (NULL == rdi->rh)
575 : {
576 0 : GNUNET_break_op (0);
577 0 : resume_abort_with_error (ac,
578 : MHD_HTTP_INTERNAL_SERVER_ERROR,
579 : TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED,
580 : "Failed to start refund with exchange");
581 0 : return;
582 : }
583 2 : ac->pending_at_ce++;
584 : }
585 : /* Still continue if no coins for this exchange were deposited. */
586 2 : if (0 == ac->pending_at_ce)
587 0 : find_next_exchange (ac);
588 : }
589 :
590 :
591 : /**
592 : * Begin of the DB transaction. If required (from
593 : * soft/serialization errors), the transaction can be
594 : * restarted here.
595 : *
596 : * @param ac abortment context to transact
597 : */
598 : static void
599 : begin_transaction (struct AbortContext *ac);
600 :
601 :
602 : /**
603 : * Find the exchange we need to talk to for the next
604 : * pending deposit permission.
605 : *
606 : * @param ac abortment context we are processing
607 : */
608 : static void
609 4 : find_next_exchange (struct AbortContext *ac)
610 : {
611 6 : for (size_t i = 0; i<ac->coins_cnt; i++)
612 : {
613 4 : struct RefundDetails *rdi = &ac->rd[i];
614 :
615 4 : if (! rdi->processed)
616 : {
617 2 : ac->current_exchange = rdi->exchange_url;
618 2 : ac->fo = TMH_EXCHANGES_keys4exchange (ac->current_exchange,
619 : false,
620 : &process_abort_with_exchange,
621 : ac);
622 2 : if (NULL == ac->fo)
623 : {
624 : /* strange, should have happened on pay! */
625 0 : GNUNET_break (0);
626 0 : resume_abort_with_error (ac,
627 : MHD_HTTP_INTERNAL_SERVER_ERROR,
628 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
629 : ac->current_exchange);
630 0 : return;
631 : }
632 2 : return;
633 : }
634 : }
635 2 : ac->current_exchange = NULL;
636 2 : GNUNET_assert (0 == ac->pending);
637 : /* We are done with all the HTTP requests, go back and try
638 : the 'big' database transaction! (It should work now!) */
639 2 : begin_transaction (ac);
640 : }
641 :
642 :
643 : /**
644 : * Function called with information about a coin that was deposited.
645 : *
646 : * @param cls closure
647 : * @param exchange_url exchange where @a coin_pub was deposited
648 : * @param coin_pub public key of the coin
649 : * @param amount_with_fee amount the exchange will deposit for this coin
650 : * @param deposit_fee fee the exchange will charge for this coin
651 : * @param refund_fee fee the exchange will charge for refunding this coin
652 : */
653 : static void
654 4 : refund_coins (void *cls,
655 : const char *exchange_url,
656 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
657 : const struct TALER_Amount *amount_with_fee,
658 : const struct TALER_Amount *deposit_fee,
659 : const struct TALER_Amount *refund_fee)
660 : {
661 4 : struct AbortContext *ac = cls;
662 : struct GNUNET_TIME_Timestamp now;
663 :
664 : (void) deposit_fee;
665 : (void) refund_fee;
666 4 : now = GNUNET_TIME_timestamp_get ();
667 8 : for (size_t i = 0; i<ac->coins_cnt; i++)
668 : {
669 4 : struct RefundDetails *rdi = &ac->rd[i];
670 : enum GNUNET_DB_QueryStatus qs;
671 :
672 4 : if ( (0 !=
673 4 : GNUNET_memcmp (coin_pub,
674 4 : &rdi->coin_pub)) ||
675 : (0 !=
676 4 : strcmp (exchange_url,
677 4 : rdi->exchange_url)) )
678 0 : continue; /* not in request */
679 4 : rdi->found_deposit = true;
680 4 : rdi->amount_with_fee = *amount_with_fee;
681 : /* Store refund in DB */
682 4 : qs = TMH_db->refund_coin (TMH_db->cls,
683 4 : ac->hc->instance->settings.id,
684 4 : &ac->h_contract_terms,
685 : now,
686 : coin_pub,
687 : /* justification */
688 : "incomplete abortment aborted");
689 4 : if (0 > qs)
690 : {
691 0 : TMH_db->rollback (TMH_db->cls);
692 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
693 : {
694 0 : begin_transaction (ac);
695 0 : return;
696 : }
697 : /* Always report on hard error as well to enable diagnostics */
698 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
699 0 : resume_abort_with_error (ac,
700 : MHD_HTTP_INTERNAL_SERVER_ERROR,
701 : TALER_EC_GENERIC_DB_STORE_FAILED,
702 : "refund_coin");
703 0 : return;
704 : }
705 : } /* for all coins */
706 : }
707 :
708 :
709 : /**
710 : * Begin of the DB transaction. If required (from soft/serialization errors),
711 : * the transaction can be restarted here.
712 : *
713 : * @param ac abortment context to transact
714 : */
715 : static void
716 4 : begin_transaction (struct AbortContext *ac)
717 : {
718 : enum GNUNET_DB_QueryStatus qs;
719 :
720 : /* Avoid re-trying transactions on soft errors forever! */
721 4 : if (ac->retry_counter++ > MAX_RETRIES)
722 : {
723 0 : GNUNET_break (0);
724 0 : resume_abort_with_error (ac,
725 : MHD_HTTP_INTERNAL_SERVER_ERROR,
726 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
727 : NULL);
728 0 : return;
729 : }
730 4 : GNUNET_assert (GNUNET_YES == ac->suspended);
731 :
732 : /* First, try to see if we have all we need already done */
733 4 : TMH_db->preflight (TMH_db->cls);
734 4 : if (GNUNET_OK !=
735 4 : TMH_db->start (TMH_db->cls,
736 : "run abort"))
737 : {
738 0 : GNUNET_break (0);
739 0 : resume_abort_with_error (ac,
740 : MHD_HTTP_INTERNAL_SERVER_ERROR,
741 : TALER_EC_GENERIC_DB_START_FAILED,
742 : NULL);
743 0 : return;
744 : }
745 :
746 : /* check payment was indeed incomplete
747 : (now that we are in the transaction scope!) */
748 : {
749 : struct TALER_PrivateContractHashP h_contract_terms;
750 : bool paid;
751 :
752 4 : qs = TMH_db->lookup_order_status (TMH_db->cls,
753 4 : ac->hc->instance->settings.id,
754 4 : ac->hc->infix,
755 : &h_contract_terms,
756 : &paid);
757 4 : switch (qs)
758 : {
759 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
760 : case GNUNET_DB_STATUS_HARD_ERROR:
761 : /* Always report on hard error to enable diagnostics */
762 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
763 0 : TMH_db->rollback (TMH_db->cls);
764 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
765 : {
766 0 : begin_transaction (ac);
767 0 : return;
768 : }
769 : /* Always report on hard error as well to enable diagnostics */
770 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
771 0 : resume_abort_with_error (ac,
772 : MHD_HTTP_INTERNAL_SERVER_ERROR,
773 : TALER_EC_GENERIC_DB_FETCH_FAILED,
774 : "order status");
775 0 : return;
776 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
777 0 : TMH_db->rollback (TMH_db->cls);
778 0 : resume_abort_with_error (ac,
779 : MHD_HTTP_NOT_FOUND,
780 : TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND,
781 : "Could not find contract");
782 0 : return;
783 4 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
784 4 : if (paid)
785 : {
786 : /* Payment is complete, refuse to abort. */
787 0 : TMH_db->rollback (TMH_db->cls);
788 0 : resume_abort_with_error (ac,
789 : MHD_HTTP_PRECONDITION_FAILED,
790 : TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
791 : "Payment was complete, refusing to abort");
792 0 : return;
793 : }
794 : }
795 4 : if (0 !=
796 4 : GNUNET_memcmp (&ac->h_contract_terms,
797 : &h_contract_terms))
798 : {
799 0 : GNUNET_break_op (0);
800 0 : TMH_db->rollback (TMH_db->cls);
801 0 : resume_abort_with_error (ac,
802 : MHD_HTTP_FORBIDDEN,
803 : TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH,
804 : "Provided hash does not match order on file");
805 0 : return;
806 : }
807 : }
808 :
809 : /* Mark all deposits we have in our database for the order as refunded. */
810 4 : qs = TMH_db->lookup_deposits (TMH_db->cls,
811 4 : ac->hc->instance->settings.id,
812 4 : &ac->h_contract_terms,
813 : &refund_coins,
814 : ac);
815 4 : if (0 > qs)
816 : {
817 0 : TMH_db->rollback (TMH_db->cls);
818 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
819 : {
820 0 : begin_transaction (ac);
821 0 : return;
822 : }
823 : /* Always report on hard error as well to enable diagnostics */
824 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
825 0 : resume_abort_with_error (ac,
826 : MHD_HTTP_INTERNAL_SERVER_ERROR,
827 : TALER_EC_GENERIC_DB_FETCH_FAILED,
828 : "deposits");
829 0 : return;
830 : }
831 :
832 4 : qs = TMH_db->commit (TMH_db->cls);
833 4 : if (0 > qs)
834 : {
835 0 : TMH_db->rollback (TMH_db->cls);
836 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
837 : {
838 0 : begin_transaction (ac);
839 0 : return;
840 : }
841 0 : resume_abort_with_error (ac,
842 : MHD_HTTP_INTERNAL_SERVER_ERROR,
843 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
844 : NULL);
845 0 : return;
846 : }
847 :
848 : /* At this point, the refund got correctly committed
849 : into the database. Tell exchange about abort/refund. */
850 4 : if (ac->pending > 0)
851 : {
852 2 : find_next_exchange (ac);
853 2 : return;
854 : }
855 2 : generate_success_response (ac);
856 : }
857 :
858 :
859 : /**
860 : * Try to parse the abort request into the given abort context.
861 : * Schedules an error response in the connection on failure.
862 : *
863 : * @param connection HTTP connection we are receiving abortment on
864 : * @param hc context we use to handle the abortment
865 : * @param ac state of the /abort call
866 : * @return #GNUNET_OK on success,
867 : * #GNUNET_NO on failure (response was queued with MHD)
868 : * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
869 : */
870 : static enum GNUNET_GenericReturnValue
871 2 : parse_abort (struct MHD_Connection *connection,
872 : struct TMH_HandlerContext *hc,
873 : struct AbortContext *ac)
874 : {
875 : const json_t *coins;
876 : struct GNUNET_JSON_Specification spec[] = {
877 2 : GNUNET_JSON_spec_array_const ("coins",
878 : &coins),
879 2 : GNUNET_JSON_spec_fixed_auto ("h_contract",
880 : &ac->h_contract_terms),
881 :
882 2 : GNUNET_JSON_spec_end ()
883 : };
884 : enum GNUNET_GenericReturnValue res;
885 :
886 2 : res = TALER_MHD_parse_json_data (connection,
887 2 : hc->request_body,
888 : spec);
889 2 : if (GNUNET_YES != res)
890 : {
891 0 : GNUNET_break_op (0);
892 0 : return res;
893 : }
894 2 : ac->coins_cnt = json_array_size (coins);
895 2 : if (0 == ac->coins_cnt)
896 : {
897 0 : GNUNET_break_op (0);
898 : return (MHD_YES ==
899 0 : TALER_MHD_reply_with_error (connection,
900 : MHD_HTTP_BAD_REQUEST,
901 : TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY,
902 : "coins"))
903 : ? GNUNET_NO
904 0 : : GNUNET_SYSERR;
905 : }
906 : /* note: 1 coin = 1 deposit confirmation expected */
907 2 : ac->pending = ac->coins_cnt;
908 2 : ac->rd = GNUNET_new_array (ac->coins_cnt,
909 : struct RefundDetails);
910 : /* This loop populates the array 'rd' in 'ac' */
911 : {
912 : unsigned int coins_index;
913 : json_t *coin;
914 4 : json_array_foreach (coins, coins_index, coin)
915 : {
916 2 : struct RefundDetails *rd = &ac->rd[coins_index];
917 : const char *exchange_url;
918 : struct GNUNET_JSON_Specification ispec[] = {
919 2 : TALER_JSON_spec_web_url ("exchange_url",
920 : &exchange_url),
921 2 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
922 : &rd->coin_pub),
923 2 : GNUNET_JSON_spec_end ()
924 : };
925 :
926 2 : res = TALER_MHD_parse_json_data (connection,
927 : coin,
928 : ispec);
929 2 : if (GNUNET_YES != res)
930 : {
931 0 : GNUNET_break_op (0);
932 0 : return res;
933 : }
934 2 : rd->exchange_url = GNUNET_strdup (exchange_url);
935 2 : rd->index = coins_index;
936 2 : rd->ac = ac;
937 : }
938 : }
939 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
940 : "Handling /abort for order `%s' with contract hash `%s'\n",
941 : ac->hc->infix,
942 : GNUNET_h2s (&ac->h_contract_terms.hash));
943 2 : return GNUNET_OK;
944 : }
945 :
946 :
947 : /**
948 : * Handle a timeout for the processing of the abort request.
949 : *
950 : * @param cls our `struct AbortContext`
951 : */
952 : static void
953 0 : handle_abort_timeout (void *cls)
954 : {
955 0 : struct AbortContext *ac = cls;
956 :
957 0 : ac->timeout_task = NULL;
958 0 : GNUNET_assert (GNUNET_YES == ac->suspended);
959 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
960 : "Resuming abort with error after timeout\n");
961 0 : if (NULL != ac->fo)
962 : {
963 0 : TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
964 0 : ac->fo = NULL;
965 : }
966 0 : resume_abort_with_error (ac,
967 : MHD_HTTP_GATEWAY_TIMEOUT,
968 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
969 : NULL);
970 0 : }
971 :
972 :
973 : MHD_RESULT
974 4 : TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
975 : struct MHD_Connection *connection,
976 : struct TMH_HandlerContext *hc)
977 : {
978 4 : struct AbortContext *ac = hc->ctx;
979 :
980 4 : if (NULL == ac)
981 : {
982 2 : ac = GNUNET_new (struct AbortContext);
983 2 : GNUNET_CONTAINER_DLL_insert (ac_head,
984 : ac_tail,
985 : ac);
986 2 : ac->connection = connection;
987 2 : ac->hc = hc;
988 2 : hc->ctx = ac;
989 2 : hc->cc = &abort_context_cleanup;
990 : }
991 4 : if (GNUNET_SYSERR == ac->suspended)
992 0 : return MHD_NO; /* during shutdown, we don't generate any more replies */
993 4 : if (0 != ac->response_code)
994 : {
995 : MHD_RESULT res;
996 :
997 : /* We are *done* processing the request,
998 : just queue the response (!) */
999 2 : if (UINT_MAX == ac->response_code)
1000 : {
1001 0 : GNUNET_break (0);
1002 0 : return MHD_NO; /* hard error */
1003 : }
1004 2 : res = MHD_queue_response (connection,
1005 : ac->response_code,
1006 : ac->response);
1007 2 : MHD_destroy_response (ac->response);
1008 2 : ac->response = NULL;
1009 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1010 : "Queueing response (%u) for /abort (%s).\n",
1011 : (unsigned int) ac->response_code,
1012 : res ? "OK" : "FAILED");
1013 2 : return res;
1014 : }
1015 : {
1016 : enum GNUNET_GenericReturnValue ret;
1017 :
1018 2 : ret = parse_abort (connection,
1019 : hc,
1020 : ac);
1021 2 : if (GNUNET_OK != ret)
1022 : return (GNUNET_NO == ret)
1023 : ? MHD_YES
1024 0 : : MHD_NO;
1025 : }
1026 :
1027 : /* Abort not finished, suspend while we interact with the exchange */
1028 2 : GNUNET_assert (GNUNET_NO == ac->suspended);
1029 2 : MHD_suspend_connection (connection);
1030 2 : ac->suspended = GNUNET_YES;
1031 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1032 : "Suspending abort handling while working with the exchange\n");
1033 2 : ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_GENERIC_TIMEOUT,
1034 : &handle_abort_timeout,
1035 : ac);
1036 2 : begin_transaction (ac);
1037 2 : return MHD_YES;
1038 : }
1039 :
1040 :
1041 : /* end of taler-merchant-httpd_post-orders-ID-abort.c */
|