Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020-2022 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 : /**
21 : * @file src/backend/taler-merchant-httpd_post-orders-ORDER_ID-refund.c
22 : * @brief handling of POST /orders/$ID/refund requests
23 : * @author Jonathan Buchanan
24 : */
25 : #include "platform.h"
26 : struct CoinRefund;
27 : #define TALER_EXCHANGE_POST_COINS_REFUND_RESULT_CLOSURE struct CoinRefund
28 : #include <taler/taler_dbevents.h>
29 : #include <taler/taler_signatures.h>
30 : #include <taler/taler_json_lib.h>
31 : #include <taler/taler_exchange_service.h>
32 : #include "taler-merchant-httpd.h"
33 : #include "taler-merchant-httpd_exchanges.h"
34 : #include "taler-merchant-httpd_get-exchanges.h"
35 : #include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h"
36 : #include "merchant-database/insert_refund_proof.h"
37 : #include "merchant-database/get_contract_terms.h"
38 : #include "merchant-database/set_instance.h"
39 : #include "merchant-database/get_refund_proof.h"
40 : #include "merchant-database/iterate_refunds_detailed.h"
41 : #include "merchant-database/event_notify.h"
42 : #include "merchant-database/preflight.h"
43 :
44 :
45 : /**
46 : * Information we keep for each coin to be refunded.
47 : */
48 : struct CoinRefund
49 : {
50 :
51 : /**
52 : * Kept in a DLL.
53 : */
54 : struct CoinRefund *next;
55 :
56 : /**
57 : * Kept in a DLL.
58 : */
59 : struct CoinRefund *prev;
60 :
61 : /**
62 : * Request to connect to the target exchange.
63 : */
64 : struct TMH_EXCHANGES_KeysOperation *fo;
65 :
66 : /**
67 : * Handle for the refund operation with the exchange.
68 : */
69 : struct TALER_EXCHANGE_PostCoinsRefundHandle *rh;
70 :
71 : /**
72 : * Request this operation is part of.
73 : */
74 : struct PostRefundData *prd;
75 :
76 : /**
77 : * URL of the exchange for this @e coin_pub.
78 : */
79 : char *exchange_url;
80 :
81 : /**
82 : * Fully reply from the exchange, only possibly set if
83 : * we got a JSON reply and a non-#MHD_HTTP_OK error code
84 : */
85 : json_t *exchange_reply;
86 :
87 : /**
88 : * When did the merchant grant the refund. To be used to group events
89 : * in the wallet.
90 : */
91 : struct GNUNET_TIME_Timestamp execution_time;
92 :
93 : /**
94 : * Coin to refund.
95 : */
96 : struct TALER_CoinSpendPublicKeyP coin_pub;
97 :
98 : /**
99 : * Refund transaction ID to use.
100 : */
101 : uint64_t rtransaction_id;
102 :
103 : /**
104 : * Unique serial number identifying the refund.
105 : */
106 : uint64_t refund_serial;
107 :
108 : /**
109 : * Amount to refund.
110 : */
111 : struct TALER_Amount refund_amount;
112 :
113 : /**
114 : * Public key of the exchange affirming the refund.
115 : */
116 : struct TALER_ExchangePublicKeyP exchange_pub;
117 :
118 : /**
119 : * Signature of the exchange affirming the refund.
120 : */
121 : struct TALER_ExchangeSignatureP exchange_sig;
122 :
123 : /**
124 : * HTTP status from the exchange, #MHD_HTTP_OK if
125 : * @a exchange_pub and @a exchange_sig are valid.
126 : */
127 : unsigned int exchange_status;
128 :
129 : /**
130 : * HTTP error code from the exchange.
131 : */
132 : enum TALER_ErrorCode exchange_code;
133 :
134 : };
135 :
136 :
137 : /**
138 : * Context for the operation.
139 : */
140 : struct PostRefundData
141 : {
142 :
143 : /**
144 : * Hashed version of contract terms. All zeros if not provided.
145 : */
146 : struct TALER_PrivateContractHashP h_contract_terms;
147 :
148 : /**
149 : * DLL of (suspended) requests.
150 : */
151 : struct PostRefundData *next;
152 :
153 : /**
154 : * DLL of (suspended) requests.
155 : */
156 : struct PostRefundData *prev;
157 :
158 : /**
159 : * Refunds for this order. Head of DLL.
160 : */
161 : struct CoinRefund *cr_head;
162 :
163 : /**
164 : * Refunds for this order. Tail of DLL.
165 : */
166 : struct CoinRefund *cr_tail;
167 :
168 : /**
169 : * Context of the request.
170 : */
171 : struct TMH_HandlerContext *hc;
172 :
173 : /**
174 : * Entry in the #resume_timeout_heap for this check payment, if we are
175 : * suspended.
176 : */
177 : struct TMH_SuspendedConnection sc;
178 :
179 : /**
180 : * order ID for the payment
181 : */
182 : const char *order_id;
183 :
184 : /**
185 : * Where to get the contract
186 : */
187 : const char *contract_url;
188 :
189 : /**
190 : * fulfillment URL of the contract (valid as long as
191 : * @e contract_terms is valid).
192 : */
193 : const char *fulfillment_url;
194 :
195 : /**
196 : * session of the client
197 : */
198 : const char *session_id;
199 :
200 : /**
201 : * Contract terms of the payment we are checking. NULL when they
202 : * are not (yet) known.
203 : */
204 : json_t *contract_terms;
205 :
206 : /**
207 : * Total refunds granted for this payment. Only initialized
208 : * if @e refunded is set to true.
209 : */
210 : struct TALER_Amount refund_amount;
211 :
212 : /**
213 : * Did we suspend @a connection and are thus in
214 : * the #prd_head DLL (#GNUNET_YES). Set to
215 : * #GNUNET_NO if we are not suspended, and to
216 : * #GNUNET_SYSERR if we should close the connection
217 : * without a response due to shutdown.
218 : */
219 : enum GNUNET_GenericReturnValue suspended;
220 :
221 : /**
222 : * Return code: #TALER_EC_NONE if successful.
223 : */
224 : enum TALER_ErrorCode ec;
225 :
226 : /**
227 : * HTTP status to use for the reply, 0 if not yet known.
228 : */
229 : unsigned int http_status;
230 :
231 : /**
232 : * Set to true if we are dealing with an unclaimed order
233 : * (and thus @e h_contract_terms is not set, and certain
234 : * DB queries will not work).
235 : */
236 : bool unclaimed;
237 :
238 : /**
239 : * Set to true if this payment has been refunded and
240 : * @e refund_amount is initialized.
241 : */
242 : bool refunded;
243 :
244 : /**
245 : * Set to true if a refund is still available for the
246 : * wallet for this payment.
247 : */
248 : bool refund_available;
249 :
250 : /**
251 : * Set to true if the client requested HTML, otherwise
252 : * we generate JSON.
253 : */
254 : bool generate_html;
255 :
256 : };
257 :
258 :
259 : /**
260 : * Head of DLL of (suspended) requests.
261 : */
262 : static struct PostRefundData *prd_head;
263 :
264 : /**
265 : * Tail of DLL of (suspended) requests.
266 : */
267 : static struct PostRefundData *prd_tail;
268 :
269 :
270 : /**
271 : * Function called when we are done processing a refund request.
272 : * Frees memory associated with @a ctx.
273 : *
274 : * @param ctx a `struct PostRefundData`
275 : */
276 : static void
277 2 : refund_cleanup (void *ctx)
278 : {
279 2 : struct PostRefundData *prd = ctx;
280 : struct CoinRefund *cr;
281 :
282 6 : while (NULL != (cr = prd->cr_head))
283 : {
284 4 : GNUNET_CONTAINER_DLL_remove (prd->cr_head,
285 : prd->cr_tail,
286 : cr);
287 4 : json_decref (cr->exchange_reply);
288 4 : GNUNET_free (cr->exchange_url);
289 4 : if (NULL != cr->fo)
290 : {
291 0 : TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
292 0 : cr->fo = NULL;
293 : }
294 4 : if (NULL != cr->rh)
295 : {
296 0 : TALER_EXCHANGE_post_coins_refund_cancel (cr->rh);
297 0 : cr->rh = NULL;
298 : }
299 4 : GNUNET_free (cr);
300 : }
301 2 : json_decref (prd->contract_terms);
302 2 : GNUNET_free (prd);
303 2 : }
304 :
305 :
306 : /**
307 : * Force resuming all suspended order lookups, needed during shutdown.
308 : */
309 : void
310 20 : TMH_force_wallet_refund_order_resume (void)
311 : {
312 : struct PostRefundData *prd;
313 :
314 20 : while (NULL != (prd = prd_head))
315 : {
316 0 : GNUNET_CONTAINER_DLL_remove (prd_head,
317 : prd_tail,
318 : prd);
319 0 : GNUNET_assert (GNUNET_YES == prd->suspended);
320 0 : prd->suspended = GNUNET_SYSERR;
321 0 : MHD_resume_connection (prd->sc.con);
322 : }
323 20 : }
324 :
325 :
326 : /**
327 : * Check if @a prd has exchange requests still pending.
328 : *
329 : * @param prd state to check
330 : * @return true if activities are still pending
331 : */
332 : static bool
333 8 : exchange_operations_pending (struct PostRefundData *prd)
334 : {
335 8 : for (struct CoinRefund *cr = prd->cr_head;
336 16 : NULL != cr;
337 8 : cr = cr->next)
338 : {
339 12 : if ( (NULL != cr->fo) ||
340 10 : (NULL != cr->rh) )
341 4 : return true;
342 : }
343 4 : return false;
344 : }
345 :
346 :
347 : /**
348 : * Check if @a prd is ready to be resumed, and if so, do it.
349 : *
350 : * @param prd refund request to be possibly ready
351 : */
352 : static void
353 4 : check_resume_prd (struct PostRefundData *prd)
354 : {
355 8 : if ( (TALER_EC_NONE == prd->ec) &&
356 4 : exchange_operations_pending (prd) )
357 2 : return;
358 2 : GNUNET_CONTAINER_DLL_remove (prd_head,
359 : prd_tail,
360 : prd);
361 2 : GNUNET_assert (prd->suspended);
362 2 : prd->suspended = GNUNET_NO;
363 2 : MHD_resume_connection (prd->sc.con);
364 2 : TALER_MHD_daemon_trigger ();
365 : }
366 :
367 :
368 : /**
369 : * Notify applications waiting for a client to obtain
370 : * a refund.
371 : *
372 : * @param prd refund request with the change
373 : */
374 : static void
375 4 : notify_refund_obtained (struct PostRefundData *prd)
376 : {
377 4 : struct TMH_OrderPayEventP refund_eh = {
378 4 : .header.size = htons (sizeof (refund_eh)),
379 4 : .header.type = htons (TALER_DBEVENT_MERCHANT_REFUND_OBTAINED),
380 4 : .merchant_pub = prd->hc->instance->merchant_pub
381 : };
382 :
383 4 : GNUNET_CRYPTO_hash (prd->order_id,
384 : strlen (prd->order_id),
385 : &refund_eh.h_order_id);
386 4 : TALER_MERCHANTDB_event_notify (TMH_db,
387 : &refund_eh.header,
388 : NULL,
389 : 0);
390 4 : }
391 :
392 :
393 : /**
394 : * Callbacks of this type are used to serve the result of submitting a
395 : * refund request to an exchange.
396 : *
397 : * @param cls a `struct CoinRefund`
398 : * @param rr response data
399 : */
400 : static void
401 4 : refund_cb (struct CoinRefund *cr,
402 : const struct TALER_EXCHANGE_PostCoinsRefundResponse *rr)
403 : {
404 4 : const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
405 :
406 4 : cr->rh = NULL;
407 4 : cr->exchange_status = hr->http_status;
408 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
409 : "Exchange refund status for coin %s is %u\n",
410 : TALER_B2S (&cr->coin_pub),
411 : hr->http_status);
412 4 : switch (hr->http_status)
413 : {
414 4 : case MHD_HTTP_OK:
415 : {
416 : enum GNUNET_DB_QueryStatus qs;
417 :
418 4 : qs = TALER_MERCHANTDB_set_instance (
419 : TMH_db,
420 4 : cr->prd->hc->instance->settings.id);
421 4 : if (0 >= qs)
422 : {
423 : /* generally, this is relatively harmless for the merchant, but let's at
424 : least log this. */
425 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
426 : "Failed to persist exchange response to /refund in database: %d\n",
427 : qs);
428 : }
429 : else
430 : {
431 4 : cr->exchange_pub = rr->details.ok.exchange_pub;
432 4 : cr->exchange_sig = rr->details.ok.exchange_sig;
433 4 : qs = TALER_MERCHANTDB_insert_refund_proof (TMH_db,
434 : cr->refund_serial,
435 : &rr->details.ok.exchange_sig,
436 : &rr->details.ok.exchange_pub);
437 4 : if (0 >= qs)
438 : {
439 : /* generally, this is relatively harmless for the merchant, but let's at
440 : least log this. */
441 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
442 : "Failed to persist exchange response to /refund in database: %d\n",
443 : qs);
444 : }
445 : else
446 : {
447 4 : notify_refund_obtained (cr->prd);
448 : }
449 4 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
450 : TALER_MERCHANTDB_set_instance (
451 : TMH_db,
452 : NULL));
453 : }
454 : }
455 4 : break;
456 0 : default:
457 0 : cr->exchange_code = hr->ec;
458 0 : cr->exchange_reply = json_incref ((json_t*) hr->reply);
459 0 : break;
460 : }
461 4 : check_resume_prd (cr->prd);
462 4 : }
463 :
464 :
465 : /**
466 : * Function called with the result of a
467 : * #TMH_EXCHANGES_keys4exchange()
468 : * operation.
469 : *
470 : * @param cls a `struct CoinRefund *`
471 : * @param keys keys of exchange, NULL on error
472 : * @param exchange representation of the exchange
473 : */
474 : static void
475 4 : exchange_found_cb (void *cls,
476 : struct TALER_EXCHANGE_Keys *keys,
477 : struct TMH_Exchange *exchange)
478 : {
479 4 : struct CoinRefund *cr = cls;
480 4 : struct PostRefundData *prd = cr->prd;
481 :
482 : (void) exchange;
483 4 : cr->fo = NULL;
484 4 : if (NULL == keys)
485 : {
486 0 : prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
487 0 : prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
488 0 : check_resume_prd (prd);
489 0 : return;
490 : }
491 8 : cr->rh = TALER_EXCHANGE_post_coins_refund_create (
492 : TMH_curl_ctx,
493 4 : cr->exchange_url,
494 : keys,
495 4 : &cr->refund_amount,
496 4 : &prd->h_contract_terms,
497 4 : &cr->coin_pub,
498 : cr->rtransaction_id,
499 4 : &prd->hc->instance->merchant_priv);
500 4 : GNUNET_assert (NULL != cr->rh);
501 4 : GNUNET_assert (TALER_EC_NONE ==
502 : TALER_EXCHANGE_post_coins_refund_start (cr->rh,
503 : &refund_cb,
504 : cr));
505 : }
506 :
507 :
508 : /**
509 : * Function called with information about a refund.
510 : * It is responsible for summing up the refund amount.
511 : *
512 : * @param cls closure
513 : * @param refund_serial unique serial number of the refund
514 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
515 : * @param coin_pub public coin from which the refund comes from
516 : * @param exchange_url URL of the exchange that issued @a coin_pub
517 : * @param rtransaction_id identificator of the refund
518 : * @param reason human-readable explanation of the refund
519 : * @param refund_amount refund amount which is being taken from @a coin_pub
520 : * @param pending true if the this refund was not yet processed by the wallet/exchange
521 : */
522 : static void
523 8 : process_refunds_cb (void *cls,
524 : uint64_t refund_serial,
525 : struct GNUNET_TIME_Timestamp timestamp,
526 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
527 : const char *exchange_url,
528 : uint64_t rtransaction_id,
529 : const char *reason,
530 : const struct TALER_Amount *refund_amount,
531 : bool pending)
532 : {
533 8 : struct PostRefundData *prd = cls;
534 : struct CoinRefund *cr;
535 :
536 8 : for (cr = prd->cr_head;
537 12 : NULL != cr;
538 4 : cr = cr->next)
539 8 : if (cr->refund_serial == refund_serial)
540 4 : return;
541 : /* already known */
542 :
543 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
544 : "Found refund of %s for coin %s with reason `%s' in database\n",
545 : TALER_amount2s (refund_amount),
546 : TALER_B2S (coin_pub),
547 : reason);
548 4 : cr = GNUNET_new (struct CoinRefund);
549 4 : cr->refund_serial = refund_serial;
550 4 : cr->exchange_url = GNUNET_strdup (exchange_url);
551 4 : cr->prd = prd;
552 4 : cr->coin_pub = *coin_pub;
553 4 : cr->rtransaction_id = rtransaction_id;
554 4 : cr->refund_amount = *refund_amount;
555 4 : cr->execution_time = timestamp;
556 4 : GNUNET_CONTAINER_DLL_insert (prd->cr_head,
557 : prd->cr_tail,
558 : cr);
559 4 : if (prd->refunded)
560 : {
561 2 : GNUNET_assert (0 <=
562 : TALER_amount_add (&prd->refund_amount,
563 : &prd->refund_amount,
564 : refund_amount));
565 2 : return;
566 : }
567 2 : prd->refund_amount = *refund_amount;
568 2 : prd->refunded = true;
569 2 : prd->refund_available |= pending;
570 : }
571 :
572 :
573 : /**
574 : * Obtain refunds for an order.
575 : *
576 : * @param rh context of the handler
577 : * @param connection the MHD connection to handle
578 : * @param[in,out] hc context with further information about the request
579 : * @return MHD result code
580 : */
581 : enum MHD_Result
582 4 : TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
583 : struct MHD_Connection *connection,
584 : struct TMH_HandlerContext *hc)
585 : {
586 4 : struct PostRefundData *prd = hc->ctx;
587 : enum GNUNET_DB_QueryStatus qs;
588 :
589 4 : if (NULL == prd)
590 : {
591 2 : prd = GNUNET_new (struct PostRefundData);
592 2 : prd->sc.con = connection;
593 2 : prd->hc = hc;
594 2 : prd->order_id = hc->infix;
595 2 : hc->ctx = prd;
596 2 : hc->cc = &refund_cleanup;
597 : {
598 : enum GNUNET_GenericReturnValue res;
599 :
600 : struct GNUNET_JSON_Specification spec[] = {
601 2 : GNUNET_JSON_spec_fixed_auto ("h_contract",
602 : &prd->h_contract_terms),
603 2 : GNUNET_JSON_spec_end ()
604 : };
605 2 : res = TALER_MHD_parse_json_data (connection,
606 2 : hc->request_body,
607 : spec);
608 2 : if (GNUNET_OK != res)
609 : return (GNUNET_NO == res)
610 : ? MHD_YES
611 0 : : MHD_NO;
612 : }
613 :
614 2 : TALER_MERCHANTDB_preflight (TMH_db);
615 : {
616 2 : json_t *contract_terms = NULL;
617 : uint64_t order_serial;
618 :
619 2 : qs = TALER_MERCHANTDB_get_contract_terms (TMH_db,
620 2 : hc->instance->settings.id,
621 2 : hc->infix,
622 : &contract_terms,
623 : &order_serial,
624 : NULL);
625 2 : if (0 > qs)
626 : {
627 : /* single, read-only SQL statements should never cause
628 : serialization problems */
629 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
630 : /* Always report on hard error as well to enable diagnostics */
631 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
632 0 : return TALER_MHD_reply_with_error (
633 : connection,
634 : MHD_HTTP_INTERNAL_SERVER_ERROR,
635 : TALER_EC_GENERIC_DB_FETCH_FAILED,
636 : "contract terms");
637 : }
638 2 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
639 : {
640 0 : GNUNET_break_op (0);
641 0 : return TALER_MHD_reply_with_error (
642 : connection,
643 : MHD_HTTP_NOT_FOUND,
644 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
645 0 : hc->infix);
646 : }
647 : {
648 : struct TALER_PrivateContractHashP h_contract_terms;
649 :
650 2 : if (GNUNET_OK !=
651 2 : TALER_JSON_contract_hash (contract_terms,
652 : &h_contract_terms))
653 : {
654 0 : GNUNET_break (0);
655 0 : json_decref (contract_terms);
656 0 : return TALER_MHD_reply_with_error (
657 : connection,
658 : MHD_HTTP_INTERNAL_SERVER_ERROR,
659 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
660 : NULL);
661 : }
662 2 : json_decref (contract_terms);
663 2 : if (0 != GNUNET_memcmp (&h_contract_terms,
664 : &prd->h_contract_terms))
665 : {
666 0 : return TALER_MHD_reply_with_error (
667 : connection,
668 : MHD_HTTP_FORBIDDEN,
669 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
670 : NULL);
671 : }
672 : }
673 : }
674 : }
675 4 : if (GNUNET_SYSERR == prd->suspended)
676 0 : return MHD_NO; /* we are in shutdown */
677 :
678 4 : if (TALER_EC_NONE != prd->ec)
679 : {
680 0 : GNUNET_break (0 != prd->http_status);
681 : /* kill pending coin refund operations immediately, just to be
682 : extra sure they don't modify 'prd' after we already created
683 : a reply (this might not be needed, but feels safer). */
684 0 : for (struct CoinRefund *cr = prd->cr_head;
685 0 : NULL != cr;
686 0 : cr = cr->next)
687 : {
688 0 : if (NULL != cr->fo)
689 : {
690 0 : TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
691 0 : cr->fo = NULL;
692 : }
693 0 : if (NULL != cr->rh)
694 : {
695 0 : TALER_EXCHANGE_post_coins_refund_cancel (cr->rh);
696 0 : cr->rh = NULL;
697 : }
698 : }
699 0 : return TALER_MHD_reply_with_error (connection,
700 : prd->http_status,
701 : prd->ec,
702 : NULL);
703 : }
704 :
705 4 : qs = TALER_MERCHANTDB_iterate_refunds_detailed (TMH_db,
706 4 : hc->instance->settings.id,
707 4 : &prd->h_contract_terms,
708 : &process_refunds_cb,
709 : prd);
710 4 : if (0 > qs)
711 : {
712 0 : GNUNET_break (0);
713 0 : return TALER_MHD_reply_with_error (connection,
714 : MHD_HTTP_INTERNAL_SERVER_ERROR,
715 : TALER_EC_GENERIC_DB_FETCH_FAILED,
716 : "detailed refunds");
717 : }
718 4 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
719 : {
720 : /* We have a contract, which means the order was paid, but there
721 : are no coins (maybe paid with a token?), and thus we cannot
722 : do any refunds */
723 0 : return TALER_MHD_reply_static (connection,
724 : MHD_HTTP_NO_CONTENT,
725 : NULL,
726 : NULL,
727 : 0);
728 : }
729 :
730 : /* Now launch exchange interactions, unless we already have the
731 : response in the database! */
732 4 : for (struct CoinRefund *cr = prd->cr_head;
733 12 : NULL != cr;
734 8 : cr = cr->next)
735 : {
736 8 : qs = TALER_MERCHANTDB_get_refund_proof (TMH_db,
737 : cr->refund_serial,
738 : &cr->exchange_sig,
739 : &cr->exchange_pub);
740 8 : switch (qs)
741 : {
742 0 : case GNUNET_DB_STATUS_HARD_ERROR:
743 : case GNUNET_DB_STATUS_SOFT_ERROR:
744 0 : return TALER_MHD_reply_with_error (connection,
745 : MHD_HTTP_INTERNAL_SERVER_ERROR,
746 : TALER_EC_GENERIC_DB_FETCH_FAILED,
747 : "refund proof");
748 4 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
749 4 : if (NULL == cr->exchange_reply)
750 : {
751 : /* We need to talk to the exchange */
752 4 : cr->fo = TMH_EXCHANGES_keys4exchange (cr->exchange_url,
753 : false,
754 : &exchange_found_cb,
755 : cr);
756 4 : if (NULL == cr->fo)
757 : {
758 0 : GNUNET_break (0);
759 0 : return TALER_MHD_reply_with_error (connection,
760 : MHD_HTTP_INTERNAL_SERVER_ERROR,
761 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
762 0 : cr->exchange_url);
763 : }
764 : }
765 4 : break;
766 4 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
767 : /* We got a reply earlier, set status accordingly */
768 4 : cr->exchange_status = MHD_HTTP_OK;
769 4 : break;
770 : }
771 : }
772 :
773 : /* Check if there are still exchange operations pending */
774 4 : if (exchange_operations_pending (prd))
775 : {
776 2 : if (GNUNET_NO == prd->suspended)
777 : {
778 2 : prd->suspended = GNUNET_YES;
779 2 : MHD_suspend_connection (connection);
780 2 : GNUNET_CONTAINER_DLL_insert (prd_head,
781 : prd_tail,
782 : prd);
783 : }
784 2 : return MHD_YES; /* we're still talking to the exchange */
785 : }
786 :
787 : {
788 : json_t *ra;
789 :
790 2 : ra = json_array ();
791 2 : GNUNET_assert (NULL != ra);
792 2 : for (struct CoinRefund *cr = prd->cr_head;
793 6 : NULL != cr;
794 4 : cr = cr->next)
795 : {
796 : json_t *refund;
797 :
798 4 : if (MHD_HTTP_OK != cr->exchange_status)
799 : {
800 0 : if (NULL == cr->exchange_reply)
801 : {
802 0 : refund = GNUNET_JSON_PACK (
803 : GNUNET_JSON_pack_string ("type",
804 : "failure"),
805 : GNUNET_JSON_pack_uint64 ("exchange_status",
806 : cr->exchange_status),
807 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
808 : cr->rtransaction_id),
809 : GNUNET_JSON_pack_data_auto ("coin_pub",
810 : &cr->coin_pub),
811 : TALER_JSON_pack_amount ("refund_amount",
812 : &cr->refund_amount),
813 : GNUNET_JSON_pack_timestamp ("execution_time",
814 : cr->execution_time));
815 : }
816 : else
817 : {
818 0 : refund = GNUNET_JSON_PACK (
819 : GNUNET_JSON_pack_string ("type",
820 : "failure"),
821 : GNUNET_JSON_pack_uint64 ("exchange_status",
822 : cr->exchange_status),
823 : GNUNET_JSON_pack_uint64 ("exchange_code",
824 : cr->exchange_code),
825 : GNUNET_JSON_pack_object_incref ("exchange_reply",
826 : cr->exchange_reply),
827 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
828 : cr->rtransaction_id),
829 : GNUNET_JSON_pack_data_auto ("coin_pub",
830 : &cr->coin_pub),
831 : TALER_JSON_pack_amount ("refund_amount",
832 : &cr->refund_amount),
833 : GNUNET_JSON_pack_timestamp ("execution_time",
834 : cr->execution_time));
835 : }
836 : }
837 : else
838 : {
839 4 : refund = GNUNET_JSON_PACK (
840 : GNUNET_JSON_pack_string ("type",
841 : "success"),
842 : GNUNET_JSON_pack_uint64 ("exchange_status",
843 : cr->exchange_status),
844 : GNUNET_JSON_pack_data_auto ("exchange_sig",
845 : &cr->exchange_sig),
846 : GNUNET_JSON_pack_data_auto ("exchange_pub",
847 : &cr->exchange_pub),
848 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
849 : cr->rtransaction_id),
850 : GNUNET_JSON_pack_data_auto ("coin_pub",
851 : &cr->coin_pub),
852 : TALER_JSON_pack_amount ("refund_amount",
853 : &cr->refund_amount),
854 : GNUNET_JSON_pack_timestamp ("execution_time",
855 : cr->execution_time));
856 : }
857 4 : GNUNET_assert (
858 : 0 ==
859 : json_array_append_new (ra,
860 : refund));
861 : }
862 :
863 2 : return TALER_MHD_REPLY_JSON_PACK (
864 : connection,
865 : MHD_HTTP_OK,
866 : TALER_JSON_pack_amount ("refund_amount",
867 : &prd->refund_amount),
868 : GNUNET_JSON_pack_array_steal ("refunds",
869 : ra),
870 : GNUNET_JSON_pack_data_auto ("merchant_pub",
871 : &hc->instance->merchant_pub));
872 : }
873 :
874 : return MHD_YES;
875 : }
876 :
877 :
878 : /* end of taler-merchant-httpd_post-orders-ORDER_ID-refund.c */
|