Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free
7 : Software 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 Lesser General Public License for more details.
12 :
13 : You should have received a copy of the GNU Lesser General Public License along with
14 : TALER; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler_merchant_pay_service.c
18 : * @brief Implementation of the the ideology
19 : * from the pay_service as copy of
20 : * merchant_api_post_order_pay.c
21 : * @author Bohdan Potuzhnyi
22 : */
23 : #include "platform.h"
24 : #include <curl/curl.h>
25 : #include <gnunet/gnunet_common.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <jansson.h>
28 : #include <microhttpd.h>
29 : #include <gnunet/gnunet_util_lib.h>
30 : #include <gnunet/gnunet_curl_lib.h>
31 : #include "taler_merchant_service.h"
32 : #include "taler_merchant_pay_service.h"
33 : #include "merchant_api_common.h"
34 : #include "merchant_api_curl_defaults.h"
35 : #include <stdio.h>
36 : #include <taler/taler_json_lib.h>
37 : #include <taler/taler_signatures.h>
38 : #include <taler/taler_exchange_service.h>
39 : #include <taler/taler_curl_lib.h>
40 :
41 : /**
42 : * @brief A Pay Handle
43 : */
44 : struct TALER_MERCHANT_OrderPayHandle
45 : {
46 : /**
47 : * Reference to the GNUNET CURL execution context.
48 : */
49 : struct GNUNET_CURL_Context *ctx;
50 :
51 : /**
52 : * Callback to invoke with the payment result ("pay" mode).
53 : */
54 : TALER_MERCHANT_OrderPayCallback cb;
55 :
56 : /**
57 : * Closure data for @a cb.
58 : */
59 : TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *cb_cls;
60 :
61 : /* Mandatory scalars: */
62 :
63 : /**
64 : * Base URL of the merchant service.
65 : */
66 : char *merchant_url;
67 :
68 : /**
69 : * Identifier of the order being paid.
70 : */
71 : char *order_id;
72 :
73 : /**
74 : * Session identifier for this payment attempt.
75 : */
76 : char *session_id;
77 :
78 : /**
79 : * Timestamp when the payment request was created.
80 : */
81 : struct GNUNET_TIME_Timestamp timestamp;
82 :
83 : /**
84 : * Deadline after which refunds are no longer allowed.
85 : */
86 : struct GNUNET_TIME_Timestamp refund_deadline;
87 :
88 : /**
89 : * Wire hash for communicating payment details.
90 : */
91 : struct TALER_MerchantWireHashP h_wire;
92 :
93 : /**
94 : * Indicates whether @a h_wire has been set.
95 : */
96 : bool has_h_wire;
97 :
98 : /* Wallet mode fields: */
99 :
100 : /**
101 : * Indicates whether a contract hash was provided.
102 : */
103 : bool has_h_contract;
104 :
105 : /**
106 : * Hash of the private contract terms (wallet mode only).
107 : */
108 : struct TALER_PrivateContractHashP h_contract_terms;
109 :
110 : /**
111 : * Indicates whether the merchant public key was provided.
112 : */
113 : bool has_merchant_pub;
114 :
115 : /**
116 : * Merchant’s public key for verifying signatures (wallet mode).
117 : */
118 : struct TALER_MerchantPublicKeyP merchant_pub;
119 :
120 : /**
121 : * Indicates whether a choice index was provided.
122 : */
123 : bool has_choice_index;
124 :
125 : /**
126 : * Selected index of the contract choice (for token operations).
127 : */
128 : int choice_index;
129 :
130 : /**
131 : * Legacy: pointer to the amount structure for strcmp checks.
132 : */
133 : const struct TALER_Amount *amount;
134 :
135 : /**
136 : * Legacy: pointer to the maximum fee structure for strcmp checks.
137 : */
138 : const struct TALER_Amount *max_fee;
139 :
140 : /* Raw arrays as passed in via set_options(): */
141 :
142 : /**
143 : * Coins used for payment.
144 : */
145 : struct
146 : {
147 : /**
148 : * Number of coins provided.
149 : */
150 : unsigned int num_coins;
151 : /**
152 : * Array of coins to spend.
153 : */
154 : const struct TALER_MERCHANT_PayCoin *coins;
155 : } coins;
156 :
157 : /**
158 : * Input tokens to use (wallet mode).
159 : */
160 : struct
161 : {
162 : /**
163 : * Number of tokens provided.
164 : */
165 : unsigned int num_tokens;
166 : /**
167 : * Array of tokens to redeem.
168 : */
169 : const struct TALER_MERCHANT_UseToken *tokens;
170 : } input_tokens;
171 :
172 : /**
173 : * Output tokens expected from the merchant.
174 : */
175 : struct
176 : {
177 : /**
178 : * Number of output tokens expected.
179 : */
180 : unsigned int num_output_tokens;
181 : /**
182 : * Array of expected output tokens.
183 : */
184 : const struct TALER_MERCHANT_OutputToken *output_tokens;
185 : } output_tokens;
186 :
187 : /**
188 : * JSON array of token envelope events (from Donau).
189 : */
190 : json_t *tokens_evs;
191 :
192 : /* Computed once both choice_index and tokens_evs are available: */
193 :
194 : /**
195 : * JSON object containing wallet-specific data payload.
196 : */
197 : json_t *wallet_data;
198 :
199 : /**
200 : * Hash code of @a wallet_data for integrity checks.
201 : */
202 : struct GNUNET_HashCode wallet_data_hash;
203 :
204 : /**
205 : * JSON body being constructed for the HTTP POST.
206 : */
207 : json_t *body;
208 :
209 : /* Final URL and CURL plumbing: */
210 :
211 : /**
212 : * Fully formed URL for the POST /order/$ID/pay request.
213 : */
214 : char *url;
215 :
216 : /**
217 : * CURL post context managing headers and body.
218 : */
219 : struct TALER_CURL_PostContext post_ctx;
220 :
221 : /**
222 : * Handle for the asynchronous CURL job.
223 : */
224 : struct GNUNET_CURL_Job *job;
225 :
226 : /**
227 : * Flags indicating which payment options have been set.
228 : */
229 : bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH];
230 :
231 : /**
232 : * True if operating in wallet mode (using tokens/contracts).
233 : */
234 : bool am_wallet;
235 :
236 : /**
237 : * Raw JSON data of `donau` for `wallet_data`.
238 : */
239 : json_t *donau_data;
240 : };
241 :
242 : /**
243 : * Parse blindly signed output tokens from response.
244 : *
245 : * @param token_sigs the JSON array with the token signatures. Can be NULL.
246 : * @param tokens where to store the parsed tokens.
247 : * @param num_tokens where to store the length of the @a tokens array.
248 : */
249 : static enum GNUNET_GenericReturnValue
250 23 : parse_tokens (const json_t *token_sigs,
251 : struct TALER_MERCHANT_OutputToken **tokens,
252 : unsigned int *num_tokens)
253 : {
254 23 : GNUNET_array_grow (*tokens,
255 : *num_tokens,
256 : json_array_size (token_sigs));
257 :
258 27 : for (unsigned int i = 0; i<(*num_tokens); i++)
259 : {
260 4 : struct TALER_MERCHANT_OutputToken *token = &(*tokens)[i];
261 : struct GNUNET_JSON_Specification spec[] = {
262 4 : TALER_JSON_spec_blinded_token_issue_sig ("blind_sig",
263 : &token->blinded_sig),
264 4 : GNUNET_JSON_spec_end ()
265 : };
266 : const json_t *jtoken
267 4 : = json_array_get (token_sigs,
268 : i);
269 :
270 4 : if (NULL == jtoken)
271 : {
272 0 : GNUNET_break (0);
273 0 : return GNUNET_SYSERR;
274 : }
275 4 : if (GNUNET_OK !=
276 4 : GNUNET_JSON_parse (jtoken,
277 : spec,
278 : NULL, NULL))
279 : {
280 0 : GNUNET_break (0);
281 0 : return GNUNET_SYSERR;
282 : }
283 : }
284 :
285 23 : return GNUNET_YES;
286 : }
287 :
288 :
289 : /**
290 : * Function called when we're done processing the
291 : * HTTP /pay request.
292 : *
293 : * @param cls the `struct TALER_MERCHANT_Pay`
294 : * @param response_code HTTP response code, 0 on error
295 : * @param resp response body, NULL if not in JSON
296 : */
297 : static void
298 35 : handle_finished (void *cls,
299 : long response_code,
300 : const void *resp)
301 : {
302 35 : struct TALER_MERCHANT_OrderPayHandle *oph = cls;
303 35 : const json_t *json = resp;
304 35 : struct TALER_MERCHANT_PayResponse pr = {
305 35 : .hr.http_status = (unsigned int) response_code,
306 : .hr.reply = json
307 : };
308 :
309 35 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
310 : "Received /pay response with status code %u\n",
311 : (unsigned int) response_code);
312 :
313 35 : json_dumpf (json,
314 : stderr,
315 : JSON_INDENT (2));
316 :
317 35 : oph->job = NULL;
318 35 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
319 : "/pay completed with response code %u\n",
320 : (unsigned int) response_code);
321 35 : switch (response_code)
322 : {
323 0 : case 0:
324 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
325 0 : break;
326 23 : case MHD_HTTP_OK:
327 23 : if (oph->am_wallet)
328 : {
329 23 : const json_t *token_sigs = NULL;
330 : struct GNUNET_JSON_Specification spec[] = {
331 23 : GNUNET_JSON_spec_fixed_auto ("sig",
332 : &pr.details.ok.merchant_sig),
333 23 : GNUNET_JSON_spec_mark_optional (
334 : GNUNET_JSON_spec_string ("pos_confirmation",
335 : &pr.details.ok.pos_confirmation),
336 : NULL),
337 23 : GNUNET_JSON_spec_mark_optional (
338 : GNUNET_JSON_spec_array_const ("token_sigs",
339 : &token_sigs),
340 : NULL),
341 23 : GNUNET_JSON_spec_end ()
342 : };
343 :
344 23 : if (GNUNET_OK !=
345 23 : GNUNET_JSON_parse (json,
346 : spec,
347 : NULL, NULL))
348 : {
349 0 : GNUNET_break_op (0);
350 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
351 0 : pr.hr.http_status = 0;
352 0 : pr.hr.hint = "sig field missing in response";
353 0 : break;
354 : }
355 :
356 23 : if (GNUNET_OK !=
357 23 : parse_tokens (token_sigs,
358 : &pr.details.ok.tokens,
359 : &pr.details.ok.num_tokens))
360 : {
361 0 : GNUNET_break_op (0);
362 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
363 0 : pr.hr.http_status = 0;
364 0 : pr.hr.hint = "failed to parse token_sigs field in response";
365 0 : break;
366 : }
367 :
368 23 : if (GNUNET_OK !=
369 23 : TALER_merchant_pay_verify (&oph->h_contract_terms,
370 23 : &oph->merchant_pub,
371 : &pr.details.ok.merchant_sig))
372 : {
373 0 : GNUNET_break_op (0);
374 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
375 0 : pr.hr.http_status = 0;
376 0 : pr.hr.hint = "signature invalid";
377 : }
378 : }
379 23 : break;
380 : /* Tolerating Not Acceptable because sometimes
381 : * - especially in tests - we might want to POST
382 : * coins one at a time. */
383 0 : case MHD_HTTP_NOT_ACCEPTABLE:
384 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
385 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
386 0 : break;
387 2 : case MHD_HTTP_BAD_REQUEST:
388 2 : pr.hr.ec = TALER_JSON_get_error_code (json);
389 2 : pr.hr.hint = TALER_JSON_get_error_hint (json);
390 : /* This should never happen, either us
391 : * or the merchant is buggy (or API version conflict);
392 : * just pass JSON reply to the application */
393 2 : break;
394 0 : case MHD_HTTP_PAYMENT_REQUIRED:
395 : /* was originally paid, but then refunded */
396 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
397 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
398 0 : break;
399 0 : case MHD_HTTP_FORBIDDEN:
400 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
401 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
402 0 : break;
403 0 : case MHD_HTTP_NOT_FOUND:
404 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
405 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
406 : /* Nothing really to verify, this should never
407 : happen, we should pass the JSON reply to the
408 : application */
409 0 : break;
410 0 : case MHD_HTTP_REQUEST_TIMEOUT:
411 0 : pr.hr.ec = TALER_JSON_get_error_code (json);
412 0 : pr.hr.hint = TALER_JSON_get_error_hint (json);
413 : /* The merchant couldn't generate a timely response, likely because
414 : it itself waited too long on the exchange.
415 : Pass on to application. */
416 0 : break;
417 10 : case MHD_HTTP_CONFLICT:
418 10 : TALER_MERCHANT_parse_error_details_ (json,
419 : MHD_HTTP_CONFLICT,
420 : &pr.hr);
421 10 : break;
422 0 : case MHD_HTTP_GONE:
423 0 : TALER_MERCHANT_parse_error_details_ (json,
424 : response_code,
425 : &pr.hr);
426 : /* The merchant says we are too late, the offer has expired or some
427 : denomination key of a coin involved has expired.
428 : Might be a disagreement in timestamps? Still, pass on to application. */
429 0 : break;
430 0 : case MHD_HTTP_PRECONDITION_FAILED:
431 0 : TALER_MERCHANT_parse_error_details_ (json,
432 : response_code,
433 : &pr.hr);
434 : /* Nothing really to verify, the merchant is blaming us for failing to
435 : satisfy some constraint (likely it does not like our exchange because
436 : of some disagreement on the PKI). We should pass the JSON reply to the
437 : application */
438 0 : break;
439 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
440 : {
441 0 : json_t *ebus = json_object_get (json,
442 : "exchange_base_urls");
443 0 : if (NULL == ebus)
444 : {
445 0 : GNUNET_break_op (0);
446 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
447 0 : pr.hr.http_status = 0;
448 0 : pr.hr.hint = "failed to parse exchange_base_urls field in response";
449 0 : break;
450 : }
451 0 : {
452 0 : size_t alen = json_array_size (ebus);
453 0 : const char *ebua[GNUNET_NZL (alen)];
454 : size_t idx;
455 : json_t *jebu;
456 0 : bool ok = true;
457 :
458 0 : GNUNET_assert (alen <= UINT_MAX);
459 0 : json_array_foreach (ebus, idx, jebu)
460 : {
461 0 : ebua[idx] = json_string_value (jebu);
462 0 : if (NULL == ebua[idx])
463 : {
464 0 : GNUNET_break_op (0);
465 0 : pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
466 0 : pr.hr.http_status = 0;
467 0 : pr.hr.hint = "non-string value in exchange_base_urls in response";
468 0 : ok = false;
469 0 : break;
470 : }
471 : }
472 0 : if (! ok)
473 0 : break;
474 : pr.details.unavailable_for_legal_reasons.num_exchanges
475 0 : = (unsigned int) alen;
476 : pr.details.unavailable_for_legal_reasons.exchanges
477 0 : = ebua;
478 0 : oph->cb (oph->cb_cls,
479 : &pr);
480 0 : TALER_MERCHANT_order_pay_cancel1 (oph);
481 0 : return;
482 : }
483 : }
484 : break;
485 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
486 0 : TALER_MERCHANT_parse_error_details_ (json,
487 : response_code,
488 : &pr.hr);
489 : /* Server had an internal issue; we should retry,
490 : but this API leaves this to the application */
491 0 : break;
492 0 : case MHD_HTTP_BAD_GATEWAY:
493 : /* Nothing really to verify, the merchant is blaming the exchange.
494 : We should pass the JSON reply to the application */
495 0 : TALER_MERCHANT_parse_error_details_ (json,
496 : response_code,
497 : &pr.hr);
498 0 : break;
499 0 : case MHD_HTTP_SERVICE_UNAVAILABLE:
500 0 : TALER_MERCHANT_parse_error_details_ (json,
501 : response_code,
502 : &pr.hr);
503 : /* Exchange couldn't respond properly; the retry is
504 : left to the application */
505 0 : break;
506 0 : case MHD_HTTP_GATEWAY_TIMEOUT:
507 0 : TALER_MERCHANT_parse_error_details_ (json,
508 : response_code,
509 : &pr.hr);
510 : /* Exchange couldn't respond in a timely fashion;
511 : the retry is left to the application */
512 0 : break;
513 0 : default:
514 0 : TALER_MERCHANT_parse_error_details_ (json,
515 : response_code,
516 : &pr.hr);
517 : /* unexpected response code */
518 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
519 : "Unexpected response code %u/%d\n",
520 : (unsigned int) response_code,
521 : (int) pr.hr.ec);
522 0 : GNUNET_break_op (0);
523 0 : break;
524 : }
525 35 : oph->cb (oph->cb_cls,
526 : &pr);
527 :
528 35 : if (pr.details.ok.tokens)
529 : {
530 4 : GNUNET_free (pr.details.ok.tokens);
531 4 : pr.details.ok.tokens = NULL;
532 4 : pr.details.ok.num_tokens = 0;
533 : }
534 :
535 35 : TALER_MERCHANT_order_pay_cancel1 (oph);
536 : }
537 :
538 :
539 : /**
540 : * @brief Create and initialize a new payment handle
541 : *
542 : * Allocates a TALER_MERCHANT_OrderPayHandle, sets up its context and
543 : * prepares an empty JSON body for the /orders/$ID/pay request.
544 : */
545 : struct TALER_MERCHANT_OrderPayHandle *
546 35 : TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx,
547 : TALER_MERCHANT_OrderPayCallback cb,
548 : TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE
549 : *cb_cls)
550 : {
551 : struct TALER_MERCHANT_OrderPayHandle *ph =
552 35 : GNUNET_new (struct TALER_MERCHANT_OrderPayHandle);
553 35 : ph->ctx = ctx;
554 35 : ph->cb = cb;
555 35 : ph->cb_cls = cb_cls;
556 35 : ph->body = json_object ();
557 35 : GNUNET_assert (ph->body);
558 35 : return ph;
559 : }
560 :
561 :
562 : /**
563 : * @brief Cancel and free a payment handle
564 : *
565 : * Aborts any in-flight CURL job, releases all JSON objects and internal
566 : * buffers, and frees the handle structure itself.
567 : */
568 : void
569 35 : TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph)
570 : {
571 35 : if (ph->job)
572 0 : GNUNET_CURL_job_cancel (ph->job);
573 35 : ph->job = NULL;
574 :
575 35 : TALER_curl_easy_post_finished (&ph->post_ctx);
576 :
577 35 : if (ph->body)
578 35 : json_decref (ph->body);
579 35 : ph->body = NULL;
580 :
581 35 : if (ph->wallet_data)
582 6 : json_decref (ph->wallet_data);
583 35 : ph->wallet_data = NULL;
584 :
585 35 : if (ph->tokens_evs)
586 6 : json_decref (ph->tokens_evs);
587 35 : ph->tokens_evs = NULL;
588 :
589 35 : if (ph->donau_data)
590 0 : json_decref (ph->donau_data);
591 :
592 35 : GNUNET_free (ph->url);
593 35 : GNUNET_free (ph->merchant_url);
594 35 : GNUNET_free (ph->session_id);
595 35 : GNUNET_free (ph->order_id);
596 35 : GNUNET_free (ph);
597 35 : }
598 :
599 :
600 : /**
601 : * @brief Store a JSON snippet under a payment option key
602 : *
603 : * Ensures that an option of type @a ot has not already been set,
604 : * then merges @a snippet into the handle's JSON @c body. Marks the
605 : * field_seen flag and frees @a snippet.
606 : *
607 : * @param ph payment handle receiving the snippet
608 : * @param ot option type under which to store @a snippet
609 : * @param snippet JSON object representing the option payload
610 : * @return #TALER_MERCHANT_OPOEC_OK if stored; appropriate error code otherwise
611 : */
612 : static enum TALER_MERCHANT_OrderPayErrorCode
613 54 : store_json_option (struct TALER_MERCHANT_OrderPayHandle *ph,
614 : enum TALER_MERCHANT_OrderPayOptionType ot,
615 : json_t *snippet)
616 : {
617 54 : if (ph->field_seen[ot])
618 : {
619 0 : json_decref (snippet);
620 0 : return TALER_MERCHANT_OPOEC_DUPLICATE_OPTION;
621 : }
622 54 : ph->field_seen[ot] = true;
623 54 : GNUNET_assert (0 == json_object_update (ph->body,
624 : snippet));
625 54 : json_decref (snippet);
626 54 : return TALER_MERCHANT_OPOEC_OK;
627 : }
628 :
629 :
630 : /**
631 : * @brief Apply user-supplied options to a payment handle
632 : *
633 : * Iterates through a NULL-terminated array of #TALER_MERCHANT_OrderPayOption
634 : * entries, validates and stores each into @a ph. Handles JSON packing and
635 : * internal state updates for coins, tokens, deadlines, Donau data, etc.
636 : */
637 : enum TALER_MERCHANT_OrderPayErrorCode
638 35 : TALER_MERCHANT_order_pay_set_options (
639 : struct TALER_MERCHANT_OrderPayHandle *ph,
640 : const struct TALER_MERCHANT_OrderPayOption options[],
641 : size_t max_options)
642 : {
643 35 : for (size_t i = 0; i < max_options
644 445 : && options[i].ot != TALER_MERCHANT_OrderPayOptionType_END; i++)
645 : {
646 410 : const struct TALER_MERCHANT_OrderPayOption *o = &options[i];
647 :
648 410 : switch (o->ot)
649 : {
650 35 : case TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL:
651 35 : ph->merchant_url = GNUNET_strdup (o->details.merchant_url);
652 35 : break;
653 :
654 9 : case TALER_MERCHANT_OrderPayOptionType_SESSION_ID:
655 9 : ph->session_id = GNUNET_strdup (o->details.session_id);
656 : /* add straight into JSON body */
657 : {
658 9 : json_t *js = GNUNET_JSON_PACK (GNUNET_JSON_pack_string ("session_id",
659 : o->details.
660 : session_id));
661 : enum TALER_MERCHANT_OrderPayErrorCode ec =
662 9 : store_json_option (ph,
663 9 : o->ot,
664 : js);
665 9 : if (TALER_MERCHANT_OPOEC_OK != ec)
666 0 : return ec;
667 9 : break;
668 : }
669 :
670 35 : case TALER_MERCHANT_OrderPayOptionType_ORDER_ID:
671 : {
672 35 : ph->order_id = GNUNET_strdup (o->details.order_id);
673 35 : break;
674 : }
675 :
676 35 : case TALER_MERCHANT_OrderPayOptionType_H_CONTRACT:
677 : {
678 35 : ph->h_contract_terms = *o->details.h_contract;
679 35 : ph->has_h_contract = true;
680 :
681 35 : break;
682 : }
683 :
684 6 : case TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX:
685 6 : ph->choice_index = o->details.choice_index;
686 6 : ph->has_choice_index = true;
687 6 : break;
688 :
689 35 : case TALER_MERCHANT_OrderPayOptionType_AMOUNT:
690 : {
691 35 : ph->amount = &o->details.amount;
692 35 : break;
693 : }
694 :
695 35 : case TALER_MERCHANT_OrderPayOptionType_MAX_FEE:
696 : {
697 35 : ph->max_fee = &o->details.max_fee;
698 35 : break;
699 : }
700 :
701 35 : case TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB:
702 : {
703 35 : ph->merchant_pub = o->details.merchant_pub;
704 35 : ph->has_merchant_pub = true;
705 :
706 35 : break;
707 : }
708 :
709 35 : case TALER_MERCHANT_OrderPayOptionType_TIMESTAMP:
710 : {
711 35 : ph->timestamp = o->details.timestamp;
712 35 : break;
713 : }
714 :
715 35 : case TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE:
716 : {
717 35 : ph->refund_deadline = o->details.refund_deadline;
718 35 : break;
719 : }
720 :
721 35 : case TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE:
722 : {
723 : /* FIXME: This one comes from the merchant_api_post_order_pay
724 : no idea do we still need it or not? */
725 35 : break;
726 : }
727 :
728 35 : case TALER_MERCHANT_OrderPayOptionType_H_WIRE:
729 : {
730 35 : ph->h_wire = o->details.h_wire;
731 35 : ph->has_h_wire = true;
732 35 : break;
733 : }
734 :
735 35 : case TALER_MERCHANT_OrderPayOptionType_COINS:
736 : /* stash for later signing */
737 35 : ph->coins.num_coins = o->details.coins.num_coins;
738 35 : ph->coins.coins = o->details.coins.coins;
739 35 : break;
740 :
741 4 : case TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS:
742 : /* stash for later signing */
743 4 : ph->input_tokens.num_tokens = o->details.input_tokens.num_tokens;
744 4 : ph->input_tokens.tokens = o->details.input_tokens.tokens;
745 4 : break;
746 :
747 6 : case TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS:
748 : /* store JSON array directly, *and* stash for hash */
749 6 : ph->output_tokens.num_output_tokens =
750 6 : o->details.output_tokens.num_output_tokens;
751 6 : ph->output_tokens.output_tokens = o->details.output_tokens.output_tokens;
752 : {
753 : /* build and store tokens_evs */
754 6 : json_t *arr = json_array ();
755 :
756 6 : GNUNET_assert (NULL != arr);
757 12 : for (unsigned j = 0; j < ph->output_tokens.num_output_tokens; j++)
758 : {
759 6 : const struct TALER_MERCHANT_OutputToken *otk =
760 6 : &ph->output_tokens.output_tokens[j];
761 6 : json_t *je = GNUNET_JSON_PACK (TALER_JSON_pack_token_envelope (NULL,
762 : &otk->
763 : envelope));
764 6 : GNUNET_assert (0 ==
765 : json_array_append_new (arr,
766 : je));
767 : }
768 :
769 6 : ph->tokens_evs = arr;
770 :
771 6 : ph->field_seen[o->ot] = true;
772 : }
773 6 : break;
774 :
775 0 : case TALER_MERCHANT_OrderPayOptionType_DONAU_URL:
776 0 : if (NULL == ph->donau_data)
777 0 : ph->donau_data = json_object ();
778 0 : GNUNET_assert (0 ==
779 : json_object_set_new (
780 : ph->donau_data,
781 : "url",
782 : json_string (o->details.donau_url)));
783 0 : break;
784 :
785 0 : case TALER_MERCHANT_OrderPayOptionType_DONAU_YEAR:
786 : {
787 0 : if (ph->donau_data == NULL)
788 0 : ph->donau_data = json_object ();
789 0 : GNUNET_assert (0 == json_object_set_new (
790 : ph->donau_data,
791 : "year",
792 : json_integer ((json_int_t) o->details.donau_year)));
793 0 : break;
794 : }
795 :
796 0 : case TALER_MERCHANT_OrderPayOptionType_DONAU_BUDIS:
797 : {
798 0 : if (ph->donau_data == NULL)
799 0 : ph->donau_data = json_object ();
800 0 : GNUNET_assert (0 == json_object_set_new (
801 : ph->donau_data,
802 : "budikeypairs",
803 : json_incref (o->details.donau_budis_json)));
804 0 : break;
805 : }
806 :
807 0 : default:
808 0 : return TALER_MERCHANT_OPOEC_UNKNOWN_OPTION;
809 : }
810 : }
811 35 : return TALER_MERCHANT_OPOEC_OK;
812 : }
813 :
814 :
815 : /**
816 : * @brief Dispatch the /orders/$ID/pay request
817 : *
818 : * Validates that all mandatory parameters (merchant_url, order_id, coins)
819 : * have been set, builds the final JSON payload, constructs the URL,
820 : * and issues an asynchronous HTTP POST. The payment handle's callback
821 : * will receive completion notifications.
822 : */
823 : enum TALER_MERCHANT_OrderPayErrorCode
824 35 : TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph)
825 : {
826 : /* all the old mandatory checks */
827 35 : if ( (! ph->merchant_url) ||
828 35 : (! ph->order_id) )
829 : {
830 0 : GNUNET_break (0);
831 0 : return TALER_MERCHANT_OPOEC_MISSING_MANDATORY;
832 : }
833 35 : if (GNUNET_YES !=
834 35 : TALER_amount_cmp_currency (ph->amount,
835 : ph->max_fee))
836 : {
837 0 : GNUNET_break (0);
838 0 : return TALER_MERCHANT_OPOEC_INVALID_VALUE;
839 : }
840 :
841 : /* build wallet_data hash for signing coins & tokens */
842 35 : if (ph->has_choice_index)
843 : {
844 : /* base fields */
845 6 : json_t *wd = GNUNET_JSON_PACK (
846 : GNUNET_JSON_pack_int64 ("choice_index",
847 : ph->choice_index),
848 : GNUNET_JSON_pack_allow_null (
849 : GNUNET_JSON_pack_array_incref ("tokens_evs",
850 : ph->tokens_evs))
851 : );
852 :
853 : /* Putting prepared donau_data into the wallet_data */
854 6 : if (ph->donau_data)
855 0 : GNUNET_assert (0 == json_object_set_new (
856 : wd,
857 : "donau",
858 : json_incref (ph->donau_data)));
859 :
860 6 : ph->wallet_data = wd;
861 :
862 6 : TALER_json_hash (ph->wallet_data,
863 : &ph->wallet_data_hash);
864 :
865 6 : store_json_option (ph,
866 : TALER_MERCHANT_OrderPayOptionType_WALLET_DATA,
867 6 : GNUNET_JSON_PACK (
868 : GNUNET_JSON_pack_object_incref ("wallet_data",
869 : ph->wallet_data)));
870 : }
871 :
872 : /* sign coins AND build the “coins” JSON in one pass */
873 : {
874 : struct TALER_Amount total_fee;
875 : struct TALER_Amount total_amount;
876 35 : json_t *arr = json_array ();
877 :
878 35 : GNUNET_assert (NULL != arr);
879 76 : for (unsigned i = 0; i < ph->coins.num_coins; i++)
880 : {
881 41 : const struct TALER_MERCHANT_PayCoin *c = &ph->coins.coins[i];
882 : struct TALER_MERCHANT_PaidCoin pc;
883 : json_t *je;
884 :
885 : /* sign */
886 : struct TALER_Amount fee;
887 : struct TALER_DenominationHashP h_denom_pub;
888 :
889 41 : TALER_denom_pub_hash (&c->denom_pub,
890 : &h_denom_pub);
891 41 : if (0 > TALER_amount_subtract (&fee,
892 : &c->amount_with_fee,
893 : &c->amount_without_fee))
894 0 : return TALER_MERCHANT_OPOEC_INVALID_VALUE;
895 :
896 :
897 41 : TALER_wallet_deposit_sign (&c->amount_with_fee,
898 : &fee,
899 41 : &ph->h_wire,
900 41 : &ph->h_contract_terms,
901 41 : (NULL != ph->wallet_data)
902 : ? &ph->wallet_data_hash
903 : : NULL,
904 41 : c->h_age_commitment,
905 : NULL,
906 : &h_denom_pub,
907 : ph->timestamp,
908 41 : &ph->merchant_pub,
909 : ph->refund_deadline,
910 : &c->coin_priv,
911 : &pc.coin_sig);
912 :
913 41 : pc.denom_pub = c->denom_pub;
914 41 : pc.denom_sig = c->denom_sig;
915 41 : pc.denom_value = c->denom_value;
916 41 : pc.amount_with_fee = c->amount_with_fee;
917 41 : pc.amount_without_fee = c->amount_without_fee;
918 41 : pc.exchange_url = c->exchange_url;
919 41 : GNUNET_CRYPTO_eddsa_key_get_public (&c->coin_priv.eddsa_priv,
920 : &pc.coin_pub.eddsa_pub);
921 :
922 : /* JSON */
923 41 : je = GNUNET_JSON_PACK (TALER_JSON_pack_amount ("contribution",
924 : &pc.amount_with_fee),
925 : GNUNET_JSON_pack_data_auto ("coin_pub",
926 : &pc.coin_pub),
927 : GNUNET_JSON_pack_string ("exchange_url",
928 : pc.exchange_url),
929 : GNUNET_JSON_pack_data_auto ("h_denom",
930 : &h_denom_pub),
931 : TALER_JSON_pack_denom_sig ("ub_sig",
932 : &pc.denom_sig),
933 : GNUNET_JSON_pack_data_auto ("coin_sig",
934 : &pc.coin_sig));
935 41 : GNUNET_assert (0 ==
936 : json_array_append_new (arr,
937 : je));
938 :
939 : /* optional totals if you need them later
940 : (kept here because they existed in the legacy code) */
941 41 : if (0 == i)
942 : {
943 31 : total_fee = fee;
944 31 : total_amount = pc.amount_with_fee;
945 : }
946 : else
947 : {
948 10 : if ( (0 >
949 10 : TALER_amount_add (&total_fee,
950 : &total_fee,
951 10 : &fee)) ||
952 : (0 >
953 10 : TALER_amount_add (&total_amount,
954 : &total_amount,
955 : &pc.amount_with_fee)) )
956 : {
957 0 : return TALER_MERCHANT_OPOEC_INVALID_VALUE;
958 : }
959 : }
960 : }
961 :
962 : /* Putting coins to the body*/
963 : {
964 : enum TALER_MERCHANT_OrderPayErrorCode ec =
965 35 : store_json_option (ph,
966 : TALER_MERCHANT_OrderPayOptionType_COINS,
967 35 : GNUNET_JSON_PACK (
968 : GNUNET_JSON_pack_array_steal ("coins",
969 : arr)
970 : ));
971 35 : if (TALER_MERCHANT_OPOEC_OK != ec)
972 : {
973 0 : return ec;
974 : }
975 : }
976 : }
977 :
978 : /* sign & pack input_tokens into used_tokens array in body */
979 35 : if (ph->input_tokens.num_tokens > 0)
980 4 : {
981 4 : struct TALER_MERCHANT_UsedToken ut[ph->input_tokens.num_tokens];
982 4 : json_t *arr = json_array ();
983 :
984 4 : GNUNET_assert (NULL != arr);
985 8 : for (unsigned i = 0; i < ph->input_tokens.num_tokens; i++)
986 : {
987 : json_t *je;
988 4 : const struct TALER_MERCHANT_UseToken *in = &ph->input_tokens.tokens[i];
989 4 : struct TALER_MERCHANT_UsedToken *t = &ut[i];
990 :
991 4 : TALER_wallet_token_use_sign (&ph->h_contract_terms,
992 4 : &ph->wallet_data_hash,
993 : &in->token_priv,
994 : &t->token_sig);
995 :
996 4 : t->ub_sig = in->ub_sig;
997 4 : t->issue_pub = in->issue_pub;
998 :
999 4 : GNUNET_CRYPTO_eddsa_key_get_public (&in->token_priv.private_key,
1000 : &t->token_pub.public_key);
1001 :
1002 4 : je = GNUNET_JSON_PACK (
1003 : GNUNET_JSON_pack_data_auto ("token_sig",
1004 : &t->token_sig),
1005 : TALER_JSON_pack_token_issue_sig ("ub_sig",
1006 : &t->ub_sig),
1007 : GNUNET_JSON_pack_data_auto ("h_issue",
1008 : &t->issue_pub.public_key->pub_key_hash),
1009 : GNUNET_JSON_pack_data_auto ("token_pub",
1010 : &t->token_pub)
1011 : );
1012 4 : GNUNET_assert (0 ==
1013 : json_array_append_new (arr,
1014 : je));
1015 : }
1016 :
1017 4 : store_json_option (ph,
1018 : TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS,
1019 4 : GNUNET_JSON_PACK (
1020 : GNUNET_JSON_pack_array_steal ("tokens",
1021 : arr)
1022 : )
1023 : );
1024 : }
1025 :
1026 :
1027 : /* post the request */
1028 : {
1029 : char *path;
1030 : CURL *eh;
1031 35 : GNUNET_asprintf (&path,
1032 : "orders/%s/pay",
1033 : ph->order_id);
1034 35 : ph->url = TALER_url_join (ph->merchant_url,
1035 : path,
1036 : NULL);
1037 35 : GNUNET_free (path);
1038 :
1039 35 : if (NULL == ph->url)
1040 : {
1041 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1042 : "Could not construct request URL.\n");
1043 0 : json_decref (ph->body);
1044 0 : GNUNET_free (ph);
1045 0 : return TALER_MERCHANT_OPOEC_URL_FAILURE;
1046 : }
1047 :
1048 35 : eh = TALER_MERCHANT_curl_easy_get_ (ph->url);
1049 35 : if (GNUNET_OK !=
1050 35 : TALER_curl_easy_post (&ph->post_ctx,
1051 : eh,
1052 35 : ph->body))
1053 : {
1054 0 : GNUNET_break (0);
1055 0 : curl_easy_cleanup (eh);
1056 0 : GNUNET_free (ph->url);
1057 0 : GNUNET_free (ph);
1058 0 : return TALER_MERCHANT_OPOEC_CURL_FAILURE;
1059 : }
1060 :
1061 70 : ph->job = GNUNET_CURL_job_add2 (ph->ctx,
1062 : eh,
1063 35 : ph->post_ctx.headers,
1064 : &handle_finished,
1065 : ph);
1066 :
1067 35 : ph->am_wallet = true;
1068 35 : return TALER_MERCHANT_OPOEC_OK;
1069 : }
1070 : }
|