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