Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (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 Lesser General Public License as
7 : published by the Free Software Foundation; either version 2.1,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU Lesser General Public License for more details.
14 :
15 : You should have received a copy of the GNU Lesser General
16 : Public License along with TALER; see the file COPYING.LGPL.
17 : If not, see <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file merchant_api_post_order_pay.c
21 : * @brief Implementation of the POST /order/$ID/pay request
22 : * of the merchant's HTTP API
23 : * @author Christian Grothoff
24 : * @author Marcello Stanisci
25 : */
26 : #include "platform.h"
27 : #include <curl/curl.h>
28 : #include <jansson.h>
29 : #include <microhttpd.h> /* just for HTTP status codes */
30 : #include <gnunet/gnunet_util_lib.h>
31 : #include <gnunet/gnunet_curl_lib.h>
32 : #include "taler_merchant_service.h"
33 : #include "merchant_api_curl_defaults.h"
34 : #include <taler/taler_json_lib.h>
35 : #include <taler/taler_signatures.h>
36 : #include <taler/taler_exchange_service.h>
37 : #include <taler/taler_curl_lib.h>
38 :
39 :
40 : /**
41 : * @brief A Pay Handle
42 : */
43 : struct TALER_MERCHANT_OrderPayHandle
44 : {
45 :
46 : /**
47 : * The url for this request.
48 : */
49 : char *url;
50 :
51 : /**
52 : * Handle for the request.
53 : */
54 : struct GNUNET_CURL_Job *job;
55 :
56 : /**
57 : * Function to call with the result in "pay" @e mode.
58 : */
59 : TALER_MERCHANT_OrderPayCallback pay_cb;
60 :
61 : /**
62 : * Closure for @a pay_cb.
63 : */
64 : void *pay_cb_cls;
65 :
66 : /**
67 : * Reference to the execution context.
68 : */
69 : struct GNUNET_CURL_Context *ctx;
70 :
71 : /**
72 : * Minor context that holds body and headers.
73 : */
74 : struct TALER_CURL_PostContext post_ctx;
75 :
76 : /**
77 : * The coins we are paying with.
78 : */
79 : struct TALER_MERCHANT_PaidCoin *coins;
80 :
81 : /**
82 : * Hash of the contract we are paying, set
83 : * if @e am_wallet is true.
84 : */
85 : struct TALER_PrivateContractHashP h_contract_terms;
86 :
87 : /**
88 : * Public key of the merchant (instance) being paid, set
89 : * if @e am_wallet is true.
90 : */
91 : struct TALER_MerchantPublicKeyP merchant_pub;
92 :
93 : /**
94 : * JSON with the full reply, used during async
95 : * processing.
96 : */
97 : json_t *full_reply;
98 :
99 : /**
100 : * Pointer into @e coins array for the coin that
101 : * created a conflict (that we are checking).
102 : */
103 : const struct TALER_MERCHANT_PaidCoin *error_pc;
104 :
105 : /**
106 : * Coin history that proves a conflict.
107 : */
108 : json_t *error_history;
109 :
110 : /**
111 : * Handle to the exchange that issued a problematic
112 : * coin (if any).
113 : */
114 : struct TALER_EXCHANGE_Handle *exchange;
115 :
116 : /**
117 : * Number of @e coins we are paying with.
118 : */
119 : unsigned int num_coins;
120 :
121 : /**
122 : * Set to true if this is the wallet API and we have
123 : * initialized @e h_contract_terms and @e merchant_pub.
124 : */
125 : bool am_wallet;
126 :
127 : };
128 :
129 :
130 : /**
131 : * We got a 409 response back from the exchange (or the merchant).
132 : * Now we need to check the provided cryptographic proof that the
133 : * coin was actually already spent!
134 : *
135 : * @param oph operation handle
136 : * @param keys key data from the exchange
137 : * @return #GNUNET_OK if conflict is valid
138 : */
139 : static enum GNUNET_GenericReturnValue
140 0 : check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
141 : const struct TALER_EXCHANGE_Keys *keys)
142 : {
143 : struct TALER_Amount spent;
144 : struct TALER_Amount spent_plus_contrib;
145 : struct TALER_DenominationHashP h_denom_pub_pc;
146 : const struct TALER_EXCHANGE_DenomPublicKey *dpk;
147 :
148 0 : TALER_denom_pub_hash (&oph->error_pc->denom_pub,
149 : &h_denom_pub_pc);
150 0 : dpk = TALER_EXCHANGE_get_denomination_key_by_hash (
151 : keys,
152 : &h_denom_pub_pc);
153 0 : if (GNUNET_OK !=
154 0 : TALER_EXCHANGE_verify_coin_history (dpk,
155 0 : &oph->error_pc->coin_pub,
156 : oph->error_history,
157 : &spent))
158 : {
159 : /* Exchange's history fails to verify */
160 0 : GNUNET_break_op (0);
161 0 : return GNUNET_SYSERR;
162 : }
163 0 : if (0 >
164 0 : TALER_amount_add (&spent_plus_contrib,
165 : &spent,
166 0 : &oph->error_pc->amount_with_fee))
167 : {
168 : /* We got an integer overflow? Bad application! */
169 0 : GNUNET_break (0);
170 0 : return GNUNET_SYSERR;
171 : }
172 0 : if (-1 != TALER_amount_cmp (&oph->error_pc->denom_value,
173 : &spent_plus_contrib))
174 : {
175 : /* according to our calculations, the transaction should
176 : have still worked, AND we did not get any proof of
177 : coin public key re-use; hence: exchange error! */
178 0 : GNUNET_break_op (0);
179 0 : return GNUNET_SYSERR;
180 : }
181 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
182 : "Accepting proof of double-spending (or coin public key re-use)\n");
183 0 : return GNUNET_OK;
184 : }
185 :
186 :
187 : /**
188 : * We got the fee structure from the exchange. Now
189 : * validate the conflict error.
190 : *
191 : * @param cls a `struct TALER_MERCHANT_OrderPayHandle`
192 : * @param ehr reply from the exchange
193 : * @param keys the key structure
194 : * @param compat protocol compatibility indication
195 : */
196 : static void
197 0 : cert_cb (void *cls,
198 : const struct TALER_EXCHANGE_HttpResponse *ehr,
199 : const struct TALER_EXCHANGE_Keys *keys,
200 : enum TALER_EXCHANGE_VersionCompatibility compat)
201 : {
202 0 : struct TALER_MERCHANT_OrderPayHandle *oph = cls;
203 :
204 0 : if (TALER_EXCHANGE_VC_INCOMPATIBLE & compat)
205 : {
206 0 : struct TALER_MERCHANT_PayResponse pr = {
207 : .hr.http_status = MHD_HTTP_CONFLICT,
208 : .hr.exchange_http_status = 0,
209 : .hr.ec = TALER_EC_WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
210 0 : .hr.reply = oph->full_reply,
211 0 : .hr.exchange_reply = ehr->reply,
212 : .hr.hint = "could not check error: incompatible exchange version"
213 : };
214 :
215 0 : oph->pay_cb (oph->pay_cb_cls,
216 : &pr);
217 0 : TALER_MERCHANT_order_pay_cancel (oph);
218 0 : return;
219 : }
220 0 : if ( (MHD_HTTP_OK != ehr->http_status) ||
221 : (NULL == keys) )
222 : {
223 0 : struct TALER_MERCHANT_PayResponse pr = {
224 : .hr.http_status = MHD_HTTP_CONFLICT,
225 0 : .hr.exchange_http_status = ehr->http_status,
226 : .hr.ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
227 0 : .hr.reply = oph->full_reply,
228 0 : .hr.exchange_reply = ehr->reply,
229 : .hr.hint = "failed to download /keys from the exchange"
230 : };
231 :
232 0 : oph->pay_cb (oph->pay_cb_cls,
233 : &pr);
234 0 : TALER_MERCHANT_order_pay_cancel (oph);
235 0 : return;
236 : }
237 :
238 0 : if (GNUNET_OK !=
239 0 : check_conflict (oph,
240 : keys))
241 : {
242 0 : struct TALER_MERCHANT_PayResponse pr = {
243 : .hr.http_status = 0,
244 : .hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE,
245 0 : .hr.reply = oph->full_reply
246 : };
247 :
248 0 : oph->pay_cb (oph->pay_cb_cls,
249 : &pr);
250 0 : TALER_MERCHANT_order_pay_cancel (oph);
251 0 : return;
252 : }
253 :
254 : {
255 0 : struct TALER_MERCHANT_PayResponse pr = {
256 : .hr.http_status = MHD_HTTP_CONFLICT,
257 0 : .hr.ec = TALER_JSON_get_error_code (oph->full_reply),
258 0 : .hr.reply = oph->full_reply
259 : };
260 :
261 0 : oph->pay_cb (oph->pay_cb_cls,
262 : &pr);
263 0 : TALER_MERCHANT_order_pay_cancel (oph);
264 : }
265 : }
266 :
267 :
268 : /**
269 : * We got a 409 response back from the exchange (or the merchant).
270 : * Now we need to check the provided cryptograophic proof that the
271 : * coin was actually already spent!
272 : *
273 : * @param[in,out] oph handle of the original pay operation
274 : * @param[in,out] pr response to modify if #GNUNET_OK is returned
275 : * @param json cryptograophic proof returned by the
276 : * exchange/merchant
277 : * @return #GNUNET_OK if proof checks out,
278 : * #GNUNET_SYSERR if it is wrong,
279 : * #GNUNET_NO if checking continues asynchronously
280 : */
281 : static enum GNUNET_GenericReturnValue
282 0 : parse_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
283 : struct TALER_MERCHANT_PayResponse *pr,
284 : const json_t *json)
285 : {
286 : json_t *ereply;
287 : const char *exchange_url;
288 : struct GNUNET_JSON_Specification spec[] = {
289 0 : GNUNET_JSON_spec_json ("exchange_reply",
290 : &ereply),
291 0 : GNUNET_JSON_spec_string ("exchange_url",
292 : &exchange_url),
293 0 : GNUNET_JSON_spec_end ()
294 : };
295 : struct TALER_CoinSpendPublicKeyP coin_pub;
296 : struct GNUNET_JSON_Specification hspec[] = {
297 0 : GNUNET_JSON_spec_json ("history",
298 : &oph->error_history),
299 0 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
300 : &coin_pub),
301 0 : GNUNET_JSON_spec_end ()
302 : };
303 0 : enum TALER_ErrorCode ec = TALER_JSON_get_error_code (json);
304 :
305 0 : switch (ec)
306 : {
307 0 : case TALER_EC_GENERIC_CURRENCY_MISMATCH:
308 : /* no proof to check, still very strange, as we
309 : should have checked that the currency matches */
310 0 : GNUNET_break_op (0);
311 0 : TALER_MERCHANT_parse_error_details_ (json,
312 : MHD_HTTP_CONFLICT,
313 : &pr->hr);
314 0 : return GNUNET_OK;
315 0 : case TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID:
316 : /* We can only be happy and accept the result;
317 : FIXME: parse the refunds... */
318 0 : TALER_MERCHANT_parse_error_details_ (json,
319 : MHD_HTTP_CONFLICT,
320 : &pr->hr);
321 0 : return GNUNET_OK;
322 0 : case TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS:
323 : /* main case, handled below */
324 0 : break;
325 0 : default:
326 0 : GNUNET_break_op (0);
327 0 : return GNUNET_SYSERR;
328 : }
329 :
330 0 : if (GNUNET_OK !=
331 0 : GNUNET_JSON_parse (json,
332 : spec,
333 : NULL, NULL))
334 : {
335 0 : GNUNET_break_op (0);
336 0 : return GNUNET_SYSERR;
337 : }
338 0 : if (GNUNET_OK !=
339 0 : GNUNET_JSON_parse (ereply,
340 : hspec,
341 : NULL, NULL))
342 : {
343 0 : GNUNET_break_op (0);
344 0 : GNUNET_JSON_parse_free (spec);
345 0 : return GNUNET_SYSERR;
346 : }
347 0 : GNUNET_JSON_parse_free (spec);
348 :
349 0 : for (unsigned int i = 0; i<oph->num_coins; i++)
350 : {
351 0 : if (0 ==
352 0 : GNUNET_memcmp (&oph->coins[i].coin_pub,
353 : &coin_pub))
354 : {
355 0 : oph->error_pc = &oph->coins[i];
356 0 : oph->full_reply = json_incref ((json_t *) json);
357 0 : oph->exchange = TALER_EXCHANGE_connect (oph->ctx,
358 0 : oph->error_pc->exchange_url,
359 : &cert_cb,
360 : oph,
361 : TALER_EXCHANGE_OPTION_END);
362 0 : return GNUNET_NO;
363 : }
364 : }
365 0 : GNUNET_break_op (0); /* complaint is not about any of the coins
366 : that we actually paid with... */
367 0 : GNUNET_JSON_parse_free (hspec);
368 0 : return GNUNET_SYSERR;
369 : }
370 :
371 :
372 : /**
373 : * Function called when we're done processing the
374 : * HTTP /pay request.
375 : *
376 : * @param cls the `struct TALER_MERCHANT_Pay`
377 : * @param response_code HTTP response code, 0 on error
378 : * @param response response body, NULL if not in JSON
379 : */
380 : static void
381 0 : handle_pay_finished (void *cls,
382 : long response_code,
383 : const void *response)
384 : {
385 0 : struct TALER_MERCHANT_OrderPayHandle *oph = cls;
386 0 : const json_t *json = response;
387 0 : struct TALER_MERCHANT_PayResponse pr = {
388 0 : .hr.http_status = (unsigned int) response_code,
389 : .hr.reply = json
390 : };
391 :
392 0 : oph->job = NULL;
393 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
394 : "/pay completed with response code %u\n",
395 : (unsigned int) response_code);
396 0 : switch (response_code)
397 : {
398 0 : case 0:
399 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
400 0 : break;
401 0 : case MHD_HTTP_OK:
402 0 : if (oph->am_wallet)
403 : {
404 : /* Here we can (and should) verify the merchant's signature */
405 : struct GNUNET_JSON_Specification spec[] = {
406 0 : GNUNET_JSON_spec_fixed_auto (
407 : "sig",
408 : &pr.details.success.merchant_sig),
409 0 : GNUNET_JSON_spec_end ()
410 : };
411 :
412 0 : if (GNUNET_OK !=
413 0 : GNUNET_JSON_parse (json,
414 : spec,
415 : NULL, NULL))
416 : {
417 0 : GNUNET_break_op (0);
418 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
419 0 : pr.hr.http_status = 0;
420 0 : pr.hr.hint = "sig field missing in response";
421 0 : break;
422 : }
423 :
424 0 : if (GNUNET_OK !=
425 0 : TALER_merchant_pay_verify (&oph->h_contract_terms,
426 0 : &oph->merchant_pub,
427 : &pr.details.success.merchant_sig))
428 : {
429 0 : GNUNET_break_op (0);
430 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
431 0 : pr.hr.http_status = 0;
432 0 : pr.hr.hint = "signature invalid";
433 : }
434 : }
435 0 : break;
436 : /* Tolerating Not Acceptable because sometimes
437 : * - especially in tests - we might want to POST
438 : * coins one at a time. */
439 0 : case MHD_HTTP_NOT_ACCEPTABLE:
440 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
441 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
442 0 : break;
443 0 : case MHD_HTTP_BAD_REQUEST:
444 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
445 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
446 : /* This should never happen, either us
447 : * or the merchant is buggy (or API version conflict);
448 : * just pass JSON reply to the application */
449 0 : break;
450 0 : case MHD_HTTP_PAYMENT_REQUIRED:
451 : /* was originally paid, but then refunded */
452 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
453 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
454 0 : break;
455 0 : case MHD_HTTP_FORBIDDEN:
456 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
457 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
458 : /* Nothing really to verify, merchant says we tried to abort the payment
459 : * after it was successful. We should pass the JSON reply to the
460 : * application */
461 0 : break;
462 0 : case MHD_HTTP_NOT_FOUND:
463 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
464 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
465 : /* Nothing really to verify, this should never
466 : happen, we should pass the JSON reply to the
467 : application */
468 0 : break;
469 0 : case MHD_HTTP_REQUEST_TIMEOUT:
470 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
471 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
472 : /* The merchant couldn't generate a timely response, likely because
473 : it itself waited too long on the exchange.
474 : Pass on to application. */
475 0 : break;
476 0 : case MHD_HTTP_CONFLICT:
477 : {
478 : enum GNUNET_GenericReturnValue ret;
479 :
480 0 : ret = parse_conflict (oph,
481 : &pr,
482 : json);
483 : switch (ret)
484 : {
485 0 : case GNUNET_OK:
486 : /* continued below, 'pr' was modified */
487 0 : break;
488 0 : case GNUNET_NO:
489 : /* handled asynchronously! */
490 0 : return; /* ! */
491 0 : case GNUNET_SYSERR:
492 0 : GNUNET_break_op (0);
493 0 : response_code = 0;
494 0 : break;
495 : }
496 0 : break;
497 : }
498 0 : case MHD_HTTP_GONE:
499 0 : TALER_MERCHANT_parse_error_details_ (json,
500 : response_code,
501 : &pr.hr);
502 : /* The merchant says we are too late, the offer has expired or some
503 : denomination key of a coin involved has expired.
504 : Might be a disagreement in timestamps? Still, pass on to application. */
505 0 : break;
506 0 : case MHD_HTTP_PRECONDITION_FAILED:
507 0 : TALER_MERCHANT_parse_error_details_ (json,
508 : response_code,
509 : &pr.hr);
510 : /* Nothing really to verify, the merchant is blaming us for failing to
511 : satisfy some constraint (likely it does not like our exchange because
512 : of some disagreement on the PKI). We should pass the JSON reply to the
513 : application */
514 0 : break;
515 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
516 0 : TALER_MERCHANT_parse_error_details_ (json,
517 : response_code,
518 : &pr.hr);
519 : /* Server had an internal issue; we should retry,
520 : but this API leaves this to the application */
521 0 : break;
522 0 : case MHD_HTTP_BAD_GATEWAY:
523 : /* Nothing really to verify, the merchant is blaming the exchange.
524 : We should pass the JSON reply to the application */
525 0 : TALER_MERCHANT_parse_error_details_ (json,
526 : response_code,
527 : &pr.hr);
528 0 : break;
529 0 : case MHD_HTTP_SERVICE_UNAVAILABLE:
530 0 : TALER_MERCHANT_parse_error_details_ (json,
531 : response_code,
532 : &pr.hr);
533 : /* Exchange couldn't respond properly; the retry is
534 : left to the application */
535 0 : break;
536 0 : case MHD_HTTP_GATEWAY_TIMEOUT:
537 0 : TALER_MERCHANT_parse_error_details_ (json,
538 : response_code,
539 : &pr.hr);
540 : /* Exchange couldn't respond in a timely fashion;
541 : the retry is left to the application */
542 0 : break;
543 0 : default:
544 0 : TALER_MERCHANT_parse_error_details_ (json,
545 : response_code,
546 : &pr.hr);
547 : /* unexpected response code */
548 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
549 : "Unexpected response code %u/%d\n",
550 : (unsigned int) response_code,
551 : (int) pr.hr.ec);
552 0 : GNUNET_break_op (0);
553 0 : break;
554 : }
555 0 : oph->pay_cb (oph->pay_cb_cls,
556 : &pr);
557 0 : TALER_MERCHANT_order_pay_cancel (oph);
558 : }
559 :
560 :
561 : struct TALER_MERCHANT_OrderPayHandle *
562 0 : TALER_MERCHANT_order_pay_frontend (
563 : struct GNUNET_CURL_Context *ctx,
564 : const char *merchant_url,
565 : const char *order_id,
566 : const char *session_id,
567 : unsigned int num_coins,
568 : const struct TALER_MERCHANT_PaidCoin coins[],
569 : TALER_MERCHANT_OrderPayCallback pay_cb,
570 : void *pay_cb_cls)
571 : {
572 : struct TALER_MERCHANT_OrderPayHandle *oph;
573 : json_t *pay_obj;
574 : json_t *j_coins;
575 : CURL *eh;
576 : struct TALER_Amount total_fee;
577 : struct TALER_Amount total_amount;
578 :
579 0 : if (0 == num_coins)
580 : {
581 0 : GNUNET_break (0);
582 0 : return NULL;
583 : }
584 0 : j_coins = json_array ();
585 0 : GNUNET_assert (NULL != j_coins);
586 0 : for (unsigned int i = 0; i<num_coins; i++)
587 : {
588 : json_t *j_coin;
589 0 : const struct TALER_MERCHANT_PaidCoin *pc = &coins[i];
590 : struct TALER_Amount fee;
591 : struct TALER_DenominationHashP denom_hash;
592 :
593 0 : if (0 >
594 0 : TALER_amount_subtract (&fee,
595 : &pc->amount_with_fee,
596 : &pc->amount_without_fee))
597 : {
598 : /* Integer underflow, fee larger than total amount?
599 : This should not happen (client violated API!) */
600 0 : GNUNET_break (0);
601 0 : json_decref (j_coins);
602 0 : return NULL;
603 : }
604 0 : if (0 == i)
605 : {
606 0 : total_fee = fee;
607 0 : total_amount = pc->amount_with_fee;
608 : }
609 : else
610 : {
611 0 : if ( (0 >
612 0 : TALER_amount_add (&total_fee,
613 : &total_fee,
614 0 : &fee)) ||
615 : (0 >
616 0 : TALER_amount_add (&total_amount,
617 : &total_amount,
618 : &pc->amount_with_fee)) )
619 : {
620 : /* integer overflow */
621 0 : GNUNET_break (0);
622 0 : json_decref (j_coins);
623 0 : return NULL;
624 : }
625 : }
626 :
627 0 : TALER_denom_pub_hash (&pc->denom_pub,
628 : &denom_hash);
629 : /* create JSON for this coin */
630 0 : j_coin = GNUNET_JSON_PACK (
631 : TALER_JSON_pack_amount ("contribution",
632 : &pc->amount_with_fee),
633 : GNUNET_JSON_pack_data_auto ("coin_pub",
634 : &pc->coin_pub),
635 : GNUNET_JSON_pack_string ("exchange_url",
636 : pc->exchange_url),
637 : GNUNET_JSON_pack_data_auto ("h_denom",
638 : &denom_hash),
639 : TALER_JSON_pack_denom_sig ("ub_sig",
640 : &pc->denom_sig),
641 : GNUNET_JSON_pack_data_auto ("coin_sig",
642 : &pc->coin_sig));
643 0 : if (0 !=
644 0 : json_array_append_new (j_coins,
645 : j_coin))
646 : {
647 0 : GNUNET_break (0);
648 0 : json_decref (j_coins);
649 0 : return NULL;
650 : }
651 : }
652 :
653 0 : pay_obj = GNUNET_JSON_PACK (
654 : GNUNET_JSON_pack_array_steal ("coins",
655 : j_coins),
656 : GNUNET_JSON_pack_allow_null (
657 : GNUNET_JSON_pack_string ("session_id",
658 : session_id)));
659 :
660 0 : oph = GNUNET_new (struct TALER_MERCHANT_OrderPayHandle);
661 0 : oph->ctx = ctx;
662 0 : oph->pay_cb = pay_cb;
663 0 : oph->pay_cb_cls = pay_cb_cls;
664 : {
665 : char *path;
666 :
667 0 : GNUNET_asprintf (&path,
668 : "orders/%s/pay",
669 : order_id);
670 0 : oph->url = TALER_url_join (merchant_url,
671 : path,
672 : NULL);
673 0 : GNUNET_free (path);
674 : }
675 0 : if (NULL == oph->url)
676 : {
677 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
678 : "Could not construct request URL.\n");
679 0 : json_decref (pay_obj);
680 0 : GNUNET_free (oph);
681 0 : return NULL;
682 : }
683 0 : oph->num_coins = num_coins;
684 0 : oph->coins = GNUNET_new_array (num_coins,
685 : struct TALER_MERCHANT_PaidCoin);
686 0 : memcpy (oph->coins,
687 : coins,
688 : num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
689 :
690 0 : eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
691 0 : if (GNUNET_OK !=
692 0 : TALER_curl_easy_post (&oph->post_ctx,
693 : eh,
694 : pay_obj))
695 : {
696 0 : GNUNET_break (0);
697 0 : curl_easy_cleanup (eh);
698 0 : json_decref (pay_obj);
699 0 : GNUNET_free (oph->url);
700 0 : GNUNET_free (oph);
701 0 : return NULL;
702 : }
703 0 : json_decref (pay_obj);
704 0 : oph->job = GNUNET_CURL_job_add2 (ctx,
705 : eh,
706 0 : oph->post_ctx.headers,
707 : &handle_pay_finished,
708 : oph);
709 0 : return oph;
710 : }
711 :
712 :
713 : struct TALER_MERCHANT_OrderPayHandle *
714 0 : TALER_MERCHANT_order_pay (
715 : struct GNUNET_CURL_Context *ctx,
716 : const char *merchant_url,
717 : const char *session_id,
718 : const struct TALER_PrivateContractHashP *h_contract_terms,
719 : const struct TALER_Amount *amount,
720 : const struct TALER_Amount *max_fee,
721 : const struct TALER_MerchantPublicKeyP *merchant_pub,
722 : const struct TALER_MerchantSignatureP *merchant_sig,
723 : struct GNUNET_TIME_Timestamp timestamp,
724 : struct GNUNET_TIME_Timestamp refund_deadline,
725 : struct GNUNET_TIME_Timestamp pay_deadline,
726 : const struct TALER_MerchantWireHashP *h_wire,
727 : const char *order_id,
728 : unsigned int num_coins,
729 : const struct TALER_MERCHANT_PayCoin coins[],
730 : TALER_MERCHANT_OrderPayCallback pay_cb,
731 : void *pay_cb_cls)
732 : {
733 0 : if (GNUNET_YES !=
734 0 : TALER_amount_cmp_currency (amount,
735 : max_fee))
736 : {
737 0 : GNUNET_break (0);
738 0 : return NULL;
739 : }
740 :
741 0 : {
742 0 : struct TALER_MERCHANT_PaidCoin pc[num_coins];
743 :
744 0 : for (unsigned int i = 0; i<num_coins; i++)
745 : {
746 0 : const struct TALER_MERCHANT_PayCoin *coin = &coins[i]; // coin priv.
747 0 : struct TALER_MERCHANT_PaidCoin *p = &pc[i]; // coin pub.
748 : struct TALER_Amount fee;
749 : struct TALER_DenominationHashP h_denom_pub;
750 :
751 0 : if (0 >
752 0 : TALER_amount_subtract (&fee,
753 : &coin->amount_with_fee,
754 : &coin->amount_without_fee))
755 : {
756 : /* Integer underflow, fee larger than total amount?
757 : This should not happen (client violated API!) */
758 0 : GNUNET_break (0);
759 0 : return NULL;
760 : }
761 0 : TALER_denom_pub_hash (&coin->denom_pub,
762 : &h_denom_pub);
763 0 : TALER_wallet_deposit_sign (&coin->amount_with_fee,
764 : &fee,
765 : h_wire,
766 : h_contract_terms,
767 : coin->h_age_commitment,
768 : NULL /* h_extensions! */,
769 : &h_denom_pub,
770 : timestamp,
771 : merchant_pub,
772 : refund_deadline,
773 : &coin->coin_priv,
774 : &p->coin_sig);
775 0 : p->denom_pub = coin->denom_pub;
776 0 : p->denom_sig = coin->denom_sig;
777 0 : p->denom_value = coin->denom_value;
778 0 : GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv,
779 : &p->coin_pub.eddsa_pub);
780 0 : p->amount_with_fee = coin->amount_with_fee;
781 0 : p->amount_without_fee = coin->amount_without_fee;
782 0 : p->exchange_url = coin->exchange_url;
783 : }
784 : {
785 : struct TALER_MERCHANT_OrderPayHandle *oph;
786 :
787 0 : oph = TALER_MERCHANT_order_pay_frontend (ctx,
788 : merchant_url,
789 : order_id,
790 : session_id,
791 : num_coins,
792 : pc,
793 : pay_cb,
794 : pay_cb_cls);
795 0 : if (NULL == oph)
796 0 : return NULL;
797 0 : oph->h_contract_terms = *h_contract_terms;
798 0 : oph->merchant_pub = *merchant_pub;
799 0 : oph->am_wallet = true;
800 0 : return oph;
801 : }
802 : }
803 : }
804 :
805 :
806 : void
807 0 : TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph)
808 : {
809 0 : if (NULL != oph->job)
810 : {
811 0 : GNUNET_CURL_job_cancel (oph->job);
812 0 : oph->job = NULL;
813 : }
814 0 : if (NULL != oph->exchange)
815 : {
816 0 : TALER_EXCHANGE_disconnect (oph->exchange);
817 0 : oph->exchange = NULL;
818 : }
819 0 : TALER_curl_easy_post_finished (&oph->post_ctx);
820 0 : json_decref (oph->error_history);
821 0 : json_decref (oph->full_reply);
822 0 : GNUNET_free (oph->coins);
823 0 : GNUNET_free (oph->url);
824 0 : GNUNET_free (oph);
825 0 : }
826 :
827 :
828 : /* end of merchant_api_post_order_pay.c */
|