Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 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
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_refund.c
19 : * @brief Implementation of the /refund request of the exchange's HTTP API
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP status codes */
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler_json_lib.h"
29 : #include "taler_exchange_service.h"
30 : #include "exchange_api_handle.h"
31 : #include "taler_signatures.h"
32 : #include "exchange_api_curl_defaults.h"
33 :
34 :
35 : /**
36 : * @brief A Refund Handle
37 : */
38 : struct TALER_EXCHANGE_RefundHandle
39 : {
40 :
41 : /**
42 : * The connection to exchange this request handle will use
43 : */
44 : struct TALER_EXCHANGE_Handle *exchange;
45 :
46 : /**
47 : * The url for this request.
48 : */
49 : char *url;
50 :
51 : /**
52 : * Context for #TEH_curl_easy_post(). Keeps the data that must
53 : * persist for Curl to make the upload.
54 : */
55 : struct TALER_CURL_PostContext ctx;
56 :
57 : /**
58 : * Handle for the request.
59 : */
60 : struct GNUNET_CURL_Job *job;
61 :
62 : /**
63 : * Function to call with the result.
64 : */
65 : TALER_EXCHANGE_RefundCallback cb;
66 :
67 : /**
68 : * Closure for @a cb.
69 : */
70 : void *cb_cls;
71 :
72 : /**
73 : * Hash over the proposal data to identify the contract
74 : * which is being refunded.
75 : */
76 : struct TALER_PrivateContractHashP h_contract_terms;
77 :
78 : /**
79 : * The coin's public key. This is the value that must have been
80 : * signed (blindly) by the Exchange.
81 : */
82 : struct TALER_CoinSpendPublicKeyP coin_pub;
83 :
84 : /**
85 : * The Merchant's public key. Allows the merchant to later refund
86 : * the transaction or to inquire about the wire transfer identifier.
87 : */
88 : struct TALER_MerchantPublicKeyP merchant;
89 :
90 : /**
91 : * Merchant-generated transaction ID for the refund.
92 : */
93 : uint64_t rtransaction_id;
94 :
95 : /**
96 : * Amount to be refunded, including refund fee charged by the
97 : * exchange to the customer.
98 : */
99 : struct TALER_Amount refund_amount;
100 :
101 : };
102 :
103 :
104 : /**
105 : * Verify that the signature on the "200 OK" response
106 : * from the exchange is valid.
107 : *
108 : * @param[in,out] rh refund handle (refund fee added)
109 : * @param json json reply with the signature
110 : * @param[out] exchange_pub set to the exchange's public key
111 : * @param[out] exchange_sig set to the exchange's signature
112 : * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
113 : */
114 : static enum GNUNET_GenericReturnValue
115 0 : verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
116 : const json_t *json,
117 : struct TALER_ExchangePublicKeyP *exchange_pub,
118 : struct TALER_ExchangeSignatureP *exchange_sig)
119 : {
120 : const struct TALER_EXCHANGE_Keys *key_state;
121 : struct GNUNET_JSON_Specification spec[] = {
122 0 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
123 : exchange_sig),
124 0 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
125 : exchange_pub),
126 0 : GNUNET_JSON_spec_end ()
127 : };
128 :
129 0 : if (GNUNET_OK !=
130 0 : GNUNET_JSON_parse (json,
131 : spec,
132 : NULL, NULL))
133 : {
134 0 : GNUNET_break_op (0);
135 0 : return GNUNET_SYSERR;
136 : }
137 0 : key_state = TALER_EXCHANGE_get_keys (rh->exchange);
138 0 : if (GNUNET_OK !=
139 0 : TALER_EXCHANGE_test_signing_key (key_state,
140 : exchange_pub))
141 : {
142 0 : GNUNET_break_op (0);
143 0 : return GNUNET_SYSERR;
144 : }
145 0 : if (GNUNET_OK !=
146 0 : TALER_exchange_online_refund_confirmation_verify (
147 0 : &rh->h_contract_terms,
148 0 : &rh->coin_pub,
149 0 : &rh->merchant,
150 : rh->rtransaction_id,
151 0 : &rh->refund_amount,
152 : exchange_pub,
153 : exchange_sig))
154 : {
155 0 : GNUNET_break_op (0);
156 0 : return GNUNET_SYSERR;
157 : }
158 0 : return GNUNET_OK;
159 : }
160 :
161 :
162 : /**
163 : * Verify that the information in the "409 Conflict" response
164 : * from the exchange is valid and indeed shows that the refund
165 : * amount requested is too high.
166 : *
167 : * @param[in,out] rh refund handle (refund fee added)
168 : * @param json json reply with the coin transaction history
169 : * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
170 : */
171 : static enum GNUNET_GenericReturnValue
172 0 : verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,
173 : const json_t *json)
174 : {
175 : json_t *history;
176 : struct TALER_DenominationHashP h_denom_pub;
177 : struct GNUNET_JSON_Specification spec[] = {
178 0 : GNUNET_JSON_spec_json ("history",
179 : &history),
180 0 : GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
181 : &h_denom_pub),
182 0 : GNUNET_JSON_spec_end ()
183 : };
184 : size_t len;
185 : struct TALER_Amount dtotal;
186 : bool have_deposit;
187 : struct TALER_Amount rtotal;
188 : bool have_refund;
189 :
190 0 : if (GNUNET_OK !=
191 0 : GNUNET_JSON_parse (json,
192 : spec,
193 : NULL, NULL))
194 : {
195 0 : GNUNET_break_op (0);
196 0 : return GNUNET_SYSERR;
197 : }
198 0 : len = json_array_size (history);
199 0 : if (0 == len)
200 : {
201 0 : GNUNET_break_op (0);
202 0 : GNUNET_JSON_parse_free (spec);
203 0 : return GNUNET_SYSERR;
204 : }
205 0 : have_deposit = false;
206 0 : have_refund = false;
207 0 : for (size_t off = 0; off<len; off++)
208 : {
209 : json_t *transaction;
210 : struct TALER_Amount amount;
211 : const char *type;
212 : struct GNUNET_JSON_Specification spec_glob[] = {
213 0 : TALER_JSON_spec_amount_any ("amount",
214 : &amount),
215 0 : GNUNET_JSON_spec_string ("type",
216 : &type),
217 0 : GNUNET_JSON_spec_end ()
218 : };
219 :
220 0 : transaction = json_array_get (history,
221 : off);
222 0 : if (GNUNET_OK !=
223 0 : GNUNET_JSON_parse (transaction,
224 : spec_glob,
225 : NULL, NULL))
226 : {
227 0 : GNUNET_break_op (0);
228 0 : GNUNET_JSON_parse_free (spec);
229 0 : return GNUNET_SYSERR;
230 : }
231 0 : if (0 == strcasecmp (type,
232 : "DEPOSIT"))
233 : {
234 : struct TALER_Amount deposit_fee;
235 : struct TALER_MerchantWireHashP h_wire;
236 : struct TALER_PrivateContractHashP h_contract_terms;
237 : struct TALER_AgeCommitmentHash h_age_commitment;
238 : bool no_hac;
239 : // struct TALER_ExtensionContractHashP h_extensions; // FIXME #7270!
240 : struct GNUNET_TIME_Timestamp wallet_timestamp;
241 : struct TALER_MerchantPublicKeyP merchant_pub;
242 : struct GNUNET_TIME_Timestamp refund_deadline;
243 : struct TALER_CoinSpendSignatureP sig;
244 : struct GNUNET_JSON_Specification ispec[] = {
245 0 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
246 : &sig),
247 0 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
248 : &h_contract_terms),
249 0 : GNUNET_JSON_spec_fixed_auto ("h_wire",
250 : &h_wire),
251 0 : GNUNET_JSON_spec_mark_optional (
252 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
253 : &h_age_commitment),
254 : &no_hac),
255 0 : GNUNET_JSON_spec_timestamp ("timestamp",
256 : &wallet_timestamp),
257 0 : GNUNET_JSON_spec_timestamp ("refund_deadline",
258 : &refund_deadline),
259 0 : TALER_JSON_spec_amount_any ("deposit_fee",
260 : &deposit_fee),
261 0 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
262 : &merchant_pub),
263 0 : GNUNET_JSON_spec_end ()
264 : };
265 :
266 0 : if (GNUNET_OK !=
267 0 : GNUNET_JSON_parse (transaction,
268 : ispec,
269 : NULL, NULL))
270 : {
271 0 : GNUNET_break_op (0);
272 0 : GNUNET_JSON_parse_free (spec);
273 0 : return GNUNET_SYSERR;
274 : }
275 0 : if (GNUNET_OK !=
276 0 : TALER_wallet_deposit_verify (&amount,
277 : &deposit_fee,
278 : &h_wire,
279 : &h_contract_terms,
280 : no_hac
281 : ? NULL
282 : : &h_age_commitment,
283 : NULL /* FIXME #7270-OEC: h_extensions! */,
284 : &h_denom_pub,
285 : wallet_timestamp,
286 : &merchant_pub,
287 : refund_deadline,
288 0 : &rh->coin_pub,
289 : &sig))
290 : {
291 0 : GNUNET_break_op (0);
292 0 : GNUNET_JSON_parse_free (spec);
293 0 : return GNUNET_SYSERR;
294 : }
295 0 : if ( (0 != GNUNET_memcmp (&rh->h_contract_terms,
296 0 : &h_contract_terms)) ||
297 0 : (0 != GNUNET_memcmp (&rh->merchant,
298 : &merchant_pub)) )
299 : {
300 : /* deposit information is about a different merchant/contract */
301 0 : GNUNET_break_op (0);
302 0 : GNUNET_JSON_parse_free (spec);
303 0 : return GNUNET_SYSERR;
304 : }
305 0 : if (have_deposit)
306 : {
307 : /* this cannot really happen, but we conservatively support it anyway */
308 0 : if (GNUNET_YES !=
309 0 : TALER_amount_cmp_currency (&amount,
310 : &dtotal))
311 : {
312 0 : GNUNET_break_op (0);
313 0 : GNUNET_JSON_parse_free (spec);
314 0 : return GNUNET_SYSERR;
315 : }
316 0 : GNUNET_break (0 <=
317 : TALER_amount_add (&dtotal,
318 : &dtotal,
319 : &amount));
320 : }
321 : else
322 : {
323 0 : dtotal = amount;
324 0 : have_deposit = true;
325 : }
326 : }
327 0 : else if (0 == strcasecmp (type,
328 : "REFUND"))
329 : {
330 : struct TALER_MerchantSignatureP sig;
331 : struct TALER_Amount refund_fee;
332 : struct TALER_Amount sig_amount;
333 : struct TALER_PrivateContractHashP h_contract_terms;
334 : uint64_t rtransaction_id;
335 : struct TALER_MerchantPublicKeyP merchant_pub;
336 : struct GNUNET_JSON_Specification ispec[] = {
337 0 : TALER_JSON_spec_amount_any ("refund_fee",
338 : &refund_fee),
339 0 : GNUNET_JSON_spec_fixed_auto ("merchant_sig",
340 : &sig),
341 0 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
342 : &h_contract_terms),
343 0 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
344 : &merchant_pub),
345 0 : GNUNET_JSON_spec_uint64 ("rtransaction_id",
346 : &rtransaction_id),
347 0 : GNUNET_JSON_spec_end ()
348 : };
349 :
350 0 : if (GNUNET_OK !=
351 0 : GNUNET_JSON_parse (transaction,
352 : ispec,
353 : NULL, NULL))
354 : {
355 0 : GNUNET_break_op (0);
356 0 : GNUNET_JSON_parse_free (spec);
357 0 : return GNUNET_SYSERR;
358 : }
359 0 : if (0 >
360 0 : TALER_amount_add (&sig_amount,
361 : &refund_fee,
362 : &amount))
363 : {
364 0 : GNUNET_break_op (0);
365 0 : GNUNET_JSON_parse_free (spec);
366 0 : return GNUNET_SYSERR;
367 : }
368 0 : if (GNUNET_OK !=
369 0 : TALER_merchant_refund_verify (&rh->coin_pub,
370 : &h_contract_terms,
371 : rtransaction_id,
372 : &sig_amount,
373 : &merchant_pub,
374 : &sig))
375 : {
376 0 : GNUNET_break_op (0);
377 0 : GNUNET_JSON_parse_free (spec);
378 0 : return GNUNET_SYSERR;
379 : }
380 0 : if ( (0 != GNUNET_memcmp (&rh->h_contract_terms,
381 0 : &h_contract_terms)) ||
382 0 : (0 != GNUNET_memcmp (&rh->merchant,
383 : &merchant_pub)) )
384 : {
385 : /* refund is about a different merchant/contract */
386 0 : GNUNET_break_op (0);
387 0 : GNUNET_JSON_parse_free (spec);
388 0 : return GNUNET_SYSERR;
389 : }
390 0 : if (rtransaction_id == rh->rtransaction_id)
391 : {
392 : /* Eh, this shows either a dependency failure or idempotency,
393 : but must not happen in a conflict reply. Fail! */
394 0 : GNUNET_break_op (0);
395 0 : GNUNET_JSON_parse_free (spec);
396 0 : return GNUNET_SYSERR;
397 : }
398 :
399 0 : if (have_refund)
400 : {
401 0 : if (GNUNET_YES !=
402 0 : TALER_amount_cmp_currency (&amount,
403 : &rtotal))
404 : {
405 0 : GNUNET_break_op (0);
406 0 : GNUNET_JSON_parse_free (spec);
407 0 : return GNUNET_SYSERR;
408 : }
409 0 : GNUNET_break (0 <=
410 : TALER_amount_add (&rtotal,
411 : &rtotal,
412 : &amount));
413 : }
414 : else
415 : {
416 0 : rtotal = amount;
417 0 : have_refund = true;
418 : }
419 : }
420 : else
421 : {
422 : /* unexpected type, new version on server? */
423 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
424 : "Unexpected type `%s' in response for exchange refund\n",
425 : type);
426 0 : GNUNET_break_op (0);
427 0 : GNUNET_JSON_parse_free (spec);
428 0 : return GNUNET_SYSERR;
429 : }
430 : }
431 :
432 0 : if (have_refund)
433 : {
434 0 : if (0 >
435 0 : TALER_amount_add (&rtotal,
436 : &rtotal,
437 0 : &rh->refund_amount))
438 : {
439 0 : GNUNET_break (0);
440 0 : GNUNET_JSON_parse_free (spec);
441 0 : return GNUNET_SYSERR;
442 : }
443 : }
444 : else
445 : {
446 0 : rtotal = rh->refund_amount;
447 0 : have_refund = true;
448 : }
449 0 : if (! have_deposit)
450 : {
451 0 : GNUNET_break (0);
452 0 : GNUNET_JSON_parse_free (spec);
453 0 : return GNUNET_SYSERR;
454 : }
455 0 : if (-1 != TALER_amount_cmp (&dtotal,
456 : &rtotal))
457 : {
458 : /* rtotal <= dtotal is fine, no conflict! */
459 0 : GNUNET_break_op (0);
460 0 : GNUNET_JSON_parse_free (spec);
461 0 : return GNUNET_SYSERR;
462 : }
463 : /* dtotal < rtotal: that's a conflict! */
464 0 : GNUNET_JSON_parse_free (spec);
465 0 : return GNUNET_OK;
466 : }
467 :
468 :
469 : /**
470 : * Verify that the information on the "412 Dependency Failed" response
471 : * from the exchange is valid and indeed shows that there is a refund
472 : * transaction ID reuse going on.
473 : *
474 : * @param[in,out] rh refund handle (refund fee added)
475 : * @param json json reply with the signature
476 : * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
477 : */
478 : static enum GNUNET_GenericReturnValue
479 0 : verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
480 : const json_t *json)
481 : {
482 : json_t *h;
483 : json_t *e;
484 : struct GNUNET_JSON_Specification spec[] = {
485 0 : GNUNET_JSON_spec_json ("history",
486 : &h),
487 0 : GNUNET_JSON_spec_end ()
488 : };
489 :
490 0 : if (GNUNET_OK !=
491 0 : GNUNET_JSON_parse (json,
492 : spec,
493 : NULL, NULL))
494 : {
495 0 : GNUNET_break_op (0);
496 0 : return GNUNET_SYSERR;
497 : }
498 0 : if ( (! json_is_array (h)) ||
499 0 : (1 != json_array_size (h) ) )
500 : {
501 0 : GNUNET_break_op (0);
502 0 : GNUNET_JSON_parse_free (spec);
503 0 : return GNUNET_SYSERR;
504 : }
505 0 : e = json_array_get (h, 0);
506 : {
507 : struct TALER_Amount amount;
508 : const char *type;
509 : struct TALER_MerchantSignatureP sig;
510 : struct TALER_Amount refund_fee;
511 : struct TALER_PrivateContractHashP h_contract_terms;
512 : uint64_t rtransaction_id;
513 : struct TALER_MerchantPublicKeyP merchant_pub;
514 : struct GNUNET_JSON_Specification ispec[] = {
515 0 : TALER_JSON_spec_amount_any ("amount",
516 : &amount),
517 0 : GNUNET_JSON_spec_string ("type",
518 : &type),
519 0 : TALER_JSON_spec_amount_any ("refund_fee",
520 : &refund_fee),
521 0 : GNUNET_JSON_spec_fixed_auto ("merchant_sig",
522 : &sig),
523 0 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
524 : &h_contract_terms),
525 0 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
526 : &merchant_pub),
527 0 : GNUNET_JSON_spec_uint64 ("rtransaction_id",
528 : &rtransaction_id),
529 0 : GNUNET_JSON_spec_end ()
530 : };
531 :
532 0 : if (GNUNET_OK !=
533 0 : GNUNET_JSON_parse (e,
534 : ispec,
535 : NULL, NULL))
536 : {
537 0 : GNUNET_break_op (0);
538 0 : GNUNET_JSON_parse_free (spec);
539 0 : return GNUNET_SYSERR;
540 : }
541 0 : if (GNUNET_OK !=
542 0 : TALER_merchant_refund_verify (&rh->coin_pub,
543 : &h_contract_terms,
544 : rtransaction_id,
545 : &amount,
546 : &merchant_pub,
547 : &sig))
548 : {
549 0 : GNUNET_break_op (0);
550 0 : GNUNET_JSON_parse_free (spec);
551 0 : return GNUNET_SYSERR;
552 : }
553 0 : if ( (rtransaction_id != rh->rtransaction_id) ||
554 0 : (0 != GNUNET_memcmp (&rh->h_contract_terms,
555 0 : &h_contract_terms)) ||
556 0 : (0 != GNUNET_memcmp (&rh->merchant,
557 0 : &merchant_pub)) ||
558 0 : (0 == TALER_amount_cmp (&rh->refund_amount,
559 : &amount)) )
560 : {
561 0 : GNUNET_break_op (0);
562 0 : GNUNET_JSON_parse_free (spec);
563 0 : return GNUNET_SYSERR;
564 : }
565 : }
566 0 : GNUNET_JSON_parse_free (spec);
567 0 : return GNUNET_OK;
568 : }
569 :
570 :
571 : /**
572 : * Function called when we're done processing the
573 : * HTTP /refund request.
574 : *
575 : * @param cls the `struct TALER_EXCHANGE_RefundHandle`
576 : * @param response_code HTTP response code, 0 on error
577 : * @param response parsed JSON result, NULL on error
578 : */
579 : static void
580 0 : handle_refund_finished (void *cls,
581 : long response_code,
582 : const void *response)
583 : {
584 0 : struct TALER_EXCHANGE_RefundHandle *rh = cls;
585 : struct TALER_ExchangePublicKeyP exchange_pub;
586 : struct TALER_ExchangeSignatureP exchange_sig;
587 0 : struct TALER_ExchangePublicKeyP *ep = NULL;
588 0 : struct TALER_ExchangeSignatureP *es = NULL;
589 0 : const json_t *j = response;
590 0 : struct TALER_EXCHANGE_HttpResponse hr = {
591 : .reply = j,
592 0 : .http_status = (unsigned int) response_code
593 : };
594 :
595 0 : rh->job = NULL;
596 0 : switch (response_code)
597 : {
598 0 : case 0:
599 0 : hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
600 0 : break;
601 0 : case MHD_HTTP_OK:
602 0 : if (GNUNET_OK !=
603 0 : verify_refund_signature_ok (rh,
604 : j,
605 : &exchange_pub,
606 : &exchange_sig))
607 : {
608 0 : GNUNET_break_op (0);
609 0 : hr.http_status = 0;
610 0 : hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
611 : }
612 : else
613 : {
614 0 : ep = &exchange_pub;
615 0 : es = &exchange_sig;
616 : }
617 0 : break;
618 0 : case MHD_HTTP_BAD_REQUEST:
619 : /* This should never happen, either us or the exchange is buggy
620 : (or API version conflict); also can happen if the currency
621 : differs (which we should obviously never support).
622 : Just pass JSON reply to the application */
623 0 : hr.ec = TALER_JSON_get_error_code (j);
624 0 : hr.hint = TALER_JSON_get_error_hint (j);
625 0 : break;
626 0 : case MHD_HTTP_FORBIDDEN:
627 : /* Nothing really to verify, exchange says one of the signatures is
628 : invalid; as we checked them, this should never happen, we
629 : should pass the JSON reply to the application */
630 0 : hr.ec = TALER_JSON_get_error_code (j);
631 0 : hr.hint = TALER_JSON_get_error_hint (j);
632 0 : break;
633 0 : case MHD_HTTP_NOT_FOUND:
634 : /* Nothing really to verify, this should never
635 : happen, we should pass the JSON reply to the application */
636 0 : hr.ec = TALER_JSON_get_error_code (j);
637 0 : hr.hint = TALER_JSON_get_error_hint (j);
638 0 : break;
639 0 : case MHD_HTTP_CONFLICT:
640 : /* Requested total refunds exceed deposited amount */
641 0 : if (GNUNET_OK !=
642 0 : verify_conflict_history_ok (rh,
643 : j))
644 : {
645 0 : GNUNET_break (0);
646 0 : json_dumpf (j,
647 : stderr,
648 : JSON_INDENT (2));
649 0 : hr.http_status = 0;
650 0 : hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
651 0 : hr.hint = "conflict information provided by exchange is invalid";
652 0 : break;
653 : }
654 0 : hr.ec = TALER_JSON_get_error_code (j);
655 0 : hr.hint = TALER_JSON_get_error_hint (j);
656 0 : break;
657 0 : case MHD_HTTP_GONE:
658 : /* Kind of normal: the money was already sent to the merchant
659 : (it was too late for the refund). */
660 0 : hr.ec = TALER_JSON_get_error_code (j);
661 0 : hr.hint = TALER_JSON_get_error_hint (j);
662 0 : break;
663 0 : case MHD_HTTP_PRECONDITION_FAILED:
664 0 : if (GNUNET_OK !=
665 0 : verify_failed_dependency_ok (rh,
666 : j))
667 : {
668 0 : GNUNET_break (0);
669 0 : hr.http_status = 0;
670 0 : hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
671 0 : hr.hint = "failed precondition proof returned by exchange is invalid";
672 0 : break;
673 : }
674 : /* Two different refund requests were made about the same deposit, but
675 : carrying identical refund transaction ids. */
676 0 : hr.ec = TALER_JSON_get_error_code (j);
677 0 : hr.hint = TALER_JSON_get_error_hint (j);
678 0 : break;
679 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
680 : /* Server had an internal issue; we should retry, but this API
681 : leaves this to the application */
682 0 : hr.ec = TALER_JSON_get_error_code (j);
683 0 : hr.hint = TALER_JSON_get_error_hint (j);
684 0 : break;
685 0 : default:
686 : /* unexpected response code */
687 0 : GNUNET_break_op (0);
688 0 : hr.ec = TALER_JSON_get_error_code (j);
689 0 : hr.hint = TALER_JSON_get_error_hint (j);
690 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
691 : "Unexpected response code %u/%d for exchange refund\n",
692 : (unsigned int) response_code,
693 : hr.ec);
694 0 : break;
695 : }
696 0 : rh->cb (rh->cb_cls,
697 : &hr,
698 : ep,
699 : es);
700 0 : TALER_EXCHANGE_refund_cancel (rh);
701 0 : }
702 :
703 :
704 : struct TALER_EXCHANGE_RefundHandle *
705 0 : TALER_EXCHANGE_refund (
706 : struct TALER_EXCHANGE_Handle *exchange,
707 : const struct TALER_Amount *amount,
708 : const struct TALER_PrivateContractHashP *h_contract_terms,
709 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
710 : uint64_t rtransaction_id,
711 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
712 : TALER_EXCHANGE_RefundCallback cb,
713 : void *cb_cls)
714 : {
715 : struct TALER_MerchantPublicKeyP merchant_pub;
716 : struct TALER_MerchantSignatureP merchant_sig;
717 : struct TALER_EXCHANGE_RefundHandle *rh;
718 : struct GNUNET_CURL_Context *ctx;
719 : json_t *refund_obj;
720 : CURL *eh;
721 : char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
722 :
723 0 : GNUNET_assert (GNUNET_YES ==
724 : TEAH_handle_is_ready (exchange));
725 0 : GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
726 : &merchant_pub.eddsa_pub);
727 0 : TALER_merchant_refund_sign (coin_pub,
728 : h_contract_terms,
729 : rtransaction_id,
730 : amount,
731 : merchant_priv,
732 : &merchant_sig);
733 : {
734 : char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
735 : char *end;
736 :
737 0 : end = GNUNET_STRINGS_data_to_string (
738 : coin_pub,
739 : sizeof (struct TALER_CoinSpendPublicKeyP),
740 : pub_str,
741 : sizeof (pub_str));
742 0 : *end = '\0';
743 0 : GNUNET_snprintf (arg_str,
744 : sizeof (arg_str),
745 : "/coins/%s/refund",
746 : pub_str);
747 : }
748 0 : refund_obj = GNUNET_JSON_PACK (
749 : TALER_JSON_pack_amount ("refund_amount",
750 : amount),
751 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
752 : h_contract_terms),
753 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
754 : rtransaction_id),
755 : GNUNET_JSON_pack_data_auto ("merchant_pub",
756 : &merchant_pub),
757 : GNUNET_JSON_pack_data_auto ("merchant_sig",
758 : &merchant_sig));
759 0 : rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle);
760 0 : rh->exchange = exchange;
761 0 : rh->cb = cb;
762 0 : rh->cb_cls = cb_cls;
763 0 : rh->url = TEAH_path_to_url (exchange,
764 : arg_str);
765 0 : if (NULL == rh->url)
766 : {
767 0 : json_decref (refund_obj);
768 0 : GNUNET_free (rh);
769 0 : return NULL;
770 : }
771 0 : rh->h_contract_terms = *h_contract_terms;
772 0 : rh->coin_pub = *coin_pub;
773 0 : rh->merchant = merchant_pub;
774 0 : rh->rtransaction_id = rtransaction_id;
775 0 : rh->refund_amount = *amount;
776 0 : eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
777 0 : if ( (NULL == eh) ||
778 : (GNUNET_OK !=
779 0 : TALER_curl_easy_post (&rh->ctx,
780 : eh,
781 : refund_obj)) )
782 : {
783 0 : GNUNET_break (0);
784 0 : if (NULL != eh)
785 0 : curl_easy_cleanup (eh);
786 0 : json_decref (refund_obj);
787 0 : GNUNET_free (rh->url);
788 0 : GNUNET_free (rh);
789 0 : return NULL;
790 : }
791 0 : json_decref (refund_obj);
792 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
793 : "URL for refund: `%s'\n",
794 : rh->url);
795 0 : ctx = TEAH_handle_to_context (exchange);
796 0 : rh->job = GNUNET_CURL_job_add2 (ctx,
797 : eh,
798 0 : rh->ctx.headers,
799 : &handle_refund_finished,
800 : rh);
801 0 : return rh;
802 : }
803 :
804 :
805 : void
806 0 : TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund)
807 : {
808 0 : if (NULL != refund->job)
809 : {
810 0 : GNUNET_CURL_job_cancel (refund->job);
811 0 : refund->job = NULL;
812 : }
813 0 : GNUNET_free (refund->url);
814 0 : TALER_curl_easy_post_finished (&refund->ctx);
815 0 : GNUNET_free (refund);
816 0 : }
817 :
818 :
819 : /* end of exchange_api_refund.c */
|