Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (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, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing_api_cmd_post_transfers.c
21 : * @brief command to test POST /transfers
22 : * @author Christian Grothoff
23 : */
24 : #include "platform.h"
25 : #include <taler/taler_exchange_service.h>
26 : #include <taler/taler_testing_lib.h>
27 : #include "taler_merchant_service.h"
28 : #include "taler_merchant_testing_lib.h"
29 :
30 :
31 : /**
32 : * State of a "POST /transfers" CMD.
33 : */
34 : struct PostTransfersState
35 : {
36 :
37 : /**
38 : * Handle for a "POST /transfers" request.
39 : */
40 : struct TALER_MERCHANT_PostTransfersHandle *pth;
41 :
42 : /**
43 : * Handle for a "GET" bank account history request.
44 : */
45 : struct TALER_BANK_DebitHistoryHandle *dhh;
46 :
47 : /**
48 : * The interpreter state.
49 : */
50 : struct TALER_TESTING_Interpreter *is;
51 :
52 : /**
53 : * Base URL of the merchant serving the request.
54 : */
55 : const char *merchant_url;
56 :
57 : /**
58 : * URL of the bank to run history on (set once @e found is set).
59 : */
60 : char *exchange_url;
61 :
62 : /**
63 : * Credit account of the merchant (set once @e found is set).
64 : */
65 : char *credit_account;
66 :
67 : /**
68 : * Payto URI to filter on.
69 : */
70 : const char *payto_uri;
71 :
72 : /**
73 : * Authentication details to authenticate to the bank.
74 : */
75 : struct TALER_BANK_AuthenticationData auth;
76 :
77 : /**
78 : * Set once we discovered the WTID and thus @e found is true.
79 : */
80 : struct TALER_WireTransferIdentifierRawP wtid;
81 :
82 : /**
83 : * the credit amount to look for at @e bank_url.
84 : */
85 : struct TALER_Amount credit_amount;
86 :
87 : /**
88 : * The fee incurred on the wire transfer.
89 : */
90 : struct TALER_Amount wire_fee;
91 :
92 : /**
93 : * Expected HTTP response code.
94 : */
95 : unsigned int http_status;
96 :
97 : /**
98 : * Array of deposit command labels we expect to see aggregated.
99 : */
100 : const char **deposits;
101 :
102 : /**
103 : * Serial number of the wire transfer in the merchant backend,
104 : * set by #TALER_TESTING_cmd_merchant_get_transfers(). 0 if unknown.
105 : */
106 : uint64_t serial;
107 :
108 : /**
109 : * Length of @e deposits.
110 : */
111 : unsigned int deposits_length;
112 :
113 : /**
114 : * Set to true once @e wtid and @e exchange_url are initialized.
115 : */
116 : bool found;
117 :
118 : /**
119 : * When the exchange executed the transfer.
120 : */
121 : struct GNUNET_TIME_Timestamp execution_time;
122 : };
123 :
124 :
125 : /**
126 : * Callback for a POST /transfers operation.
127 : *
128 : * @param cls closure for this function
129 : * @param hr HTTP response details
130 : * @param execution_time when did the transfer happen (according to the exchange),
131 : * #GNUNET_TIME_UNIT_FOREVER_ABS if the transfer did not yet happen or if
132 : * we have no data from the exchange about it
133 : * @param total_amount total amount of the wire transfer, or NULL if the exchange did
134 : * not provide any details
135 : * @param wire_fee how much did the exchange charge in terms of wire fees, or NULL
136 : * if the exchange did not provide any details
137 : * @param details_length length of the @a details array
138 : * @param details array with details about the combined transactions
139 : */
140 : static void
141 0 : transfers_cb (void *cls,
142 : const struct TALER_MERCHANT_HttpResponse *hr,
143 : struct GNUNET_TIME_Timestamp execution_time,
144 : const struct TALER_Amount *total_amount,
145 : const struct TALER_Amount *wire_fee,
146 : unsigned int details_length,
147 : const struct TALER_MERCHANT_TrackTransferDetail details[])
148 : {
149 0 : struct PostTransfersState *pts = cls;
150 :
151 0 : pts->pth = NULL;
152 0 : if (pts->http_status != hr->http_status)
153 : {
154 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
155 : "Unexpected response code %u (%d) to command %s\n",
156 : hr->http_status,
157 : (int) hr->ec,
158 : TALER_TESTING_interpreter_get_current_label (pts->is));
159 0 : TALER_TESTING_interpreter_fail (pts->is);
160 0 : return;
161 : }
162 0 : switch (hr->http_status)
163 : {
164 0 : case MHD_HTTP_OK:
165 : {
166 0 : pts->execution_time = execution_time;
167 0 : pts->wire_fee = *wire_fee;
168 0 : fprintf (stderr,
169 : "FIXME");
170 0 : json_dumpf (hr->reply,
171 : stderr,
172 : 0);
173 : #if FIXME_WRITE_PROPPER_CHECK_OF_RETURNED_DATA_HERE
174 : /* this code is some legacy logic that is close to what we
175 : need but needs to be updated to the current API */
176 : struct TALER_Amount total;
177 :
178 : if (0 >
179 : TALER_amount_subtract (&total,
180 : total_amount,
181 : wire_fee))
182 : {
183 : GNUNET_break (0);
184 : TALER_TESTING_interpreter_fail (pts->is);
185 : return;
186 : }
187 : if (0 !=
188 : TALER_amount_cmp (&total,
189 : &pts->credit_amount))
190 : {
191 : GNUNET_break (0);
192 : TALER_TESTING_interpreter_fail (pts->is);
193 : return;
194 : }
195 : TALER_amount_set_zero (total.currency,
196 : &total);
197 : for (unsigned int i = 0; i<details_length; i++)
198 : {
199 : const struct TALER_MERCHANT_TrackTransferDetail *tdd = &details[i];
200 : struct TALER_Amount sum;
201 : struct TALER_Amount fees;
202 :
203 : TALER_amount_set_zero (tdd->deposit_value.currency,
204 : &sum);
205 : TALER_amount_set_zero (tdd->deposit_fee.currency,
206 : &fees);
207 : for (unsigned int j = 0; j<pts->deposits_length; j++)
208 : {
209 : const char *label = pts->deposits[j];
210 : const struct TALER_TESTING_Command *cmd;
211 : const json_t *contract_terms;
212 : const struct TALER_Amount *deposit_value;
213 : const struct TALER_Amount *deposit_fee;
214 : const char *order_id;
215 :
216 : cmd = TALER_TESTING_interpreter_lookup_command (pts->is,
217 : label);
218 : if (NULL == cmd)
219 : {
220 : GNUNET_break (0);
221 : TALER_TESTING_interpreter_fail (pts->is);
222 : return;
223 : }
224 : if ( (GNUNET_OK !=
225 : TALER_TESTING_get_trait_contract_terms (cmd,
226 : 0,
227 : &contract_terms)) ||
228 : (GNUNET_OK !=
229 : TALER_TESTING_get_trait_amount_obj (cmd,
230 : TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_VALUE,
231 : &deposit_value)) ||
232 : (GNUNET_OK !=
233 : TALER_TESTING_get_trait_amount_obj (cmd,
234 : TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_FEE,
235 : &deposit_fee)) )
236 : {
237 : GNUNET_break (0);
238 : TALER_TESTING_interpreter_fail (pts->is);
239 : return;
240 : }
241 : order_id = json_string_value (json_object_get (contract_terms,
242 : "order_id"));
243 : if (NULL == order_id)
244 : {
245 : GNUNET_break (0);
246 : TALER_TESTING_interpreter_fail (pts->is);
247 : return;
248 : }
249 : if (0 != strcmp (tdd->order_id,
250 : order_id))
251 : continue;
252 : if (0 >
253 : TALER_amount_add (&sum,
254 : &sum,
255 : deposit_value))
256 : {
257 : GNUNET_break (0);
258 : TALER_TESTING_interpreter_fail (pts->is);
259 : return;
260 : }
261 : if (0 >
262 : TALER_amount_add (&fees,
263 : &fees,
264 : deposit_fee))
265 : {
266 : GNUNET_break (0);
267 : TALER_TESTING_interpreter_fail (pts->is);
268 : return;
269 : }
270 : }
271 : if (0 !=
272 : TALER_amount_cmp (&sum,
273 : &tdd->deposit_value))
274 : {
275 : GNUNET_break (0);
276 : TALER_TESTING_interpreter_fail (pts->is);
277 : return;
278 : }
279 : if (0 !=
280 : TALER_amount_cmp (&fees,
281 : &tdd->deposit_fee))
282 : {
283 : GNUNET_break (0);
284 : TALER_TESTING_interpreter_fail (pts->is);
285 : return;
286 : }
287 : GNUNET_assert (0 <=
288 : TALER_amount_add (&total,
289 : &total,
290 : &tdd->deposit_value));
291 : GNUNET_assert (0 <=
292 : TALER_amount_subtract (&total,
293 : &total,
294 : &tdd->deposit_fee));
295 : }
296 : if (0 !=
297 : TALER_amount_cmp (&total,
298 : &pts->credit_amount))
299 : {
300 : GNUNET_break (0);
301 : TALER_TESTING_interpreter_fail (pts->is);
302 : return;
303 : }
304 : #endif
305 0 : break;
306 : }
307 0 : case MHD_HTTP_ACCEPTED:
308 0 : break;
309 0 : case MHD_HTTP_UNAUTHORIZED:
310 0 : break;
311 0 : case MHD_HTTP_NOT_FOUND:
312 0 : break;
313 0 : case MHD_HTTP_GATEWAY_TIMEOUT:
314 0 : break;
315 0 : default:
316 0 : GNUNET_break (0);
317 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
318 : "Unhandled HTTP status %u for POST /transfers.\n",
319 : hr->http_status);
320 : }
321 0 : TALER_TESTING_interpreter_next (pts->is);
322 : }
323 :
324 :
325 : /**
326 : * Offers information from the POST /transfers CMD state to other
327 : * commands.
328 : *
329 : * @param cls closure
330 : * @param[out] ret result (could be anything)
331 : * @param trait name of the trait
332 : * @param index index number of the object to extract.
333 : * @return #GNUNET_OK on success
334 : */
335 : static enum GNUNET_GenericReturnValue
336 0 : post_transfers_traits (void *cls,
337 : const void **ret,
338 : const char *trait,
339 : unsigned int index)
340 : {
341 0 : struct PostTransfersState *pts = cls;
342 : struct TALER_TESTING_Trait traits[] = {
343 0 : TALER_TESTING_make_trait_wtid (&pts->wtid),
344 0 : TALER_TESTING_make_trait_credit_payto_uri (
345 0 : (const char **) &pts->credit_account),
346 0 : TALER_TESTING_make_trait_amount (&pts->credit_amount),
347 0 : TALER_TESTING_make_trait_fee (&pts->wire_fee),
348 0 : TALER_TESTING_make_trait_exchange_url (
349 0 : (const char **) &pts->exchange_url),
350 0 : TALER_TESTING_make_trait_timestamp (0,
351 0 : &pts->execution_time),
352 0 : TALER_TESTING_make_trait_bank_row (&pts->serial),
353 0 : TALER_TESTING_trait_end (),
354 : };
355 :
356 0 : return TALER_TESTING_get_trait (traits,
357 : ret,
358 : trait,
359 : index);
360 : }
361 :
362 :
363 : /**
364 : * Run the "POST /transfers" CMD. First, get the bank history to find
365 : * the wtid.
366 : *
367 : * @param cls closure.
368 : * @param cmd command being run now.
369 : * @param is interpreter state.
370 : */
371 : static void
372 0 : post_transfers_run2 (void *cls,
373 : const struct TALER_TESTING_Command *cmd,
374 : struct TALER_TESTING_Interpreter *is)
375 : {
376 0 : struct PostTransfersState *pts = cls;
377 :
378 0 : pts->is = is;
379 0 : pts->pth = TALER_MERCHANT_transfers_post (pts->is->ctx,
380 : pts->merchant_url,
381 0 : &pts->credit_amount,
382 0 : &pts->wtid,
383 0 : pts->credit_account,
384 0 : pts->exchange_url,
385 : &transfers_cb,
386 : pts);
387 0 : GNUNET_assert (NULL != pts->pth);
388 0 : }
389 :
390 :
391 : /**
392 : * Callbacks of this type are used to serve the result of asking
393 : * the bank for the debit transaction history.
394 : *
395 : * @param cls closure with a `struct PostTransfersState *`
396 : * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request
397 : * 0 if the bank's reply is bogus (fails to follow the protocol),
398 : * #MHD_HTTP_NO_CONTENT if there are no more results; on success the
399 : * last callback is always of this status (even if `abs(num_results)` were
400 : * already returned).
401 : * @param ec detailed error code
402 : * @param serial_id monotonically increasing counter corresponding to the transaction
403 : * @param details details about the wire transfer
404 : * @param json detailed response from the HTTPD, or NULL if reply was not in JSON
405 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
406 : */
407 : static int
408 0 : debit_cb (
409 : void *cls,
410 : unsigned int http_status,
411 : enum TALER_ErrorCode ec,
412 : uint64_t serial_id,
413 : const struct TALER_BANK_DebitDetails *details,
414 : const json_t *json)
415 : {
416 0 : struct PostTransfersState *pts = cls;
417 :
418 0 : if (MHD_HTTP_NO_CONTENT == http_status)
419 : {
420 0 : pts->dhh = NULL;
421 0 : if (! pts->found)
422 : {
423 0 : GNUNET_break (0);
424 0 : TALER_TESTING_interpreter_fail (pts->is);
425 0 : return GNUNET_OK;
426 : }
427 0 : GNUNET_assert (NULL != pts->exchange_url);
428 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
429 : "Bank transfer found, checking with merchant backend at %s about %s from %s to %s with %s\n",
430 : pts->merchant_url,
431 : TALER_amount2s (&pts->credit_amount),
432 : pts->payto_uri,
433 : pts->exchange_url,
434 : TALER_B2S (&pts->wtid));
435 0 : pts->pth = TALER_MERCHANT_transfers_post (pts->is->ctx,
436 : pts->merchant_url,
437 0 : &pts->credit_amount,
438 0 : &pts->wtid,
439 0 : pts->credit_account,
440 0 : pts->exchange_url,
441 : &transfers_cb,
442 : pts);
443 0 : GNUNET_assert (NULL != pts->pth);
444 0 : return GNUNET_OK;
445 : }
446 0 : if (MHD_HTTP_OK != http_status)
447 : {
448 0 : GNUNET_break (0);
449 0 : TALER_TESTING_interpreter_fail (pts->is);
450 0 : pts->dhh = NULL;
451 0 : return GNUNET_SYSERR;
452 : }
453 0 : if (pts->found)
454 0 : return GNUNET_OK;
455 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
456 : "Bank reports transfer of %s to %s\n",
457 : TALER_amount2s (&details->amount),
458 : details->credit_account_uri);
459 0 : if (0 != TALER_amount_cmp (&pts->credit_amount,
460 : &details->amount))
461 0 : return GNUNET_OK;
462 0 : pts->found = true;
463 0 : pts->wtid = details->wtid;
464 0 : pts->credit_account = GNUNET_strdup (details->credit_account_uri);
465 0 : pts->exchange_url = GNUNET_strdup (details->exchange_base_url);
466 0 : return GNUNET_OK;
467 : }
468 :
469 :
470 : /**
471 : * Run the "POST /transfers" CMD. First, get the bank history to find
472 : * the wtid.
473 : *
474 : * @param cls closure.
475 : * @param cmd command being run now.
476 : * @param is interpreter state.
477 : */
478 : static void
479 0 : post_transfers_run (void *cls,
480 : const struct TALER_TESTING_Command *cmd,
481 : struct TALER_TESTING_Interpreter *is)
482 : {
483 0 : struct PostTransfersState *pts = cls;
484 :
485 0 : pts->is = is;
486 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
487 : "Looking for transfer of %s from %s at bank\n",
488 : TALER_amount2s (&pts->credit_amount),
489 : pts->payto_uri);
490 0 : pts->dhh = TALER_BANK_debit_history (is->ctx,
491 0 : &pts->auth,
492 : UINT64_MAX,
493 : -INT64_MAX,
494 0 : GNUNET_TIME_UNIT_ZERO,
495 : &debit_cb,
496 : pts);
497 0 : GNUNET_assert (NULL != pts->dhh);
498 0 : }
499 :
500 :
501 : /**
502 : * Free the state of a "POST product" CMD, and possibly
503 : * cancel a pending operation thereof.
504 : *
505 : * @param cls closure.
506 : * @param cmd command being run.
507 : */
508 : static void
509 0 : post_transfers_cleanup (void *cls,
510 : const struct TALER_TESTING_Command *cmd)
511 : {
512 0 : struct PostTransfersState *pts = cls;
513 :
514 0 : if (NULL != pts->pth)
515 : {
516 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
517 : "POST /transfers operation did not complete\n");
518 0 : TALER_MERCHANT_transfers_post_cancel (pts->pth);
519 : }
520 0 : if (NULL != pts->dhh)
521 : {
522 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
523 : "GET debit history operation did not complete\n");
524 0 : TALER_BANK_debit_history_cancel (pts->dhh);
525 : }
526 0 : GNUNET_array_grow (pts->deposits,
527 : pts->deposits_length,
528 : 0);
529 0 : GNUNET_free (pts->exchange_url);
530 0 : GNUNET_free (pts->credit_account);
531 0 : GNUNET_free (pts);
532 0 : }
533 :
534 :
535 : struct TALER_TESTING_Command
536 0 : TALER_TESTING_cmd_merchant_post_transfer (
537 : const char *label,
538 : const struct TALER_BANK_AuthenticationData *auth,
539 : const char *payto_uri,
540 : const char *merchant_url,
541 : const char *credit_amount,
542 : unsigned int http_code,
543 : ...)
544 : {
545 : struct PostTransfersState *pts;
546 :
547 0 : pts = GNUNET_new (struct PostTransfersState);
548 0 : pts->merchant_url = merchant_url;
549 0 : pts->auth = *auth;
550 0 : pts->payto_uri = payto_uri;
551 0 : GNUNET_assert (GNUNET_OK ==
552 : TALER_string_to_amount (credit_amount,
553 : &pts->credit_amount));
554 0 : pts->http_status = http_code;
555 : {
556 : const char *clabel;
557 : va_list ap;
558 :
559 0 : va_start (ap, http_code);
560 0 : while (NULL != (clabel = va_arg (ap, const char *)))
561 : {
562 0 : GNUNET_array_append (pts->deposits,
563 : pts->deposits_length,
564 : clabel);
565 : }
566 0 : va_end (ap);
567 : }
568 : {
569 0 : struct TALER_TESTING_Command cmd = {
570 : .cls = pts,
571 : .label = label,
572 : .run = &post_transfers_run,
573 : .cleanup = &post_transfers_cleanup,
574 : .traits = &post_transfers_traits
575 : };
576 :
577 0 : return cmd;
578 : }
579 : }
580 :
581 :
582 : struct TALER_TESTING_Command
583 0 : TALER_TESTING_cmd_merchant_post_transfer2 (
584 : const char *label,
585 : const char *merchant_url,
586 : const char *payto_uri,
587 : const char *credit_amount,
588 : const char *wtid,
589 : const char *exchange_url,
590 : unsigned int http_code)
591 : {
592 : struct PostTransfersState *pts;
593 :
594 0 : pts = GNUNET_new (struct PostTransfersState);
595 0 : pts->merchant_url = merchant_url;
596 0 : pts->credit_account = GNUNET_strdup (payto_uri);
597 0 : pts->exchange_url = GNUNET_strdup (exchange_url);
598 0 : GNUNET_assert (GNUNET_OK ==
599 : TALER_string_to_amount (credit_amount,
600 : &pts->credit_amount));
601 0 : if (NULL == wtid)
602 : {
603 0 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
604 0 : &pts->wtid,
605 : sizeof (pts->wtid));
606 : }
607 : else
608 : {
609 0 : GNUNET_assert (GNUNET_OK ==
610 : GNUNET_STRINGS_string_to_data (wtid,
611 : strlen (wtid),
612 : &pts->wtid,
613 : sizeof (pts->wtid)));
614 : }
615 0 : pts->http_status = http_code;
616 : {
617 0 : struct TALER_TESTING_Command cmd = {
618 : .cls = pts,
619 : .label = label,
620 : .run = &post_transfers_run2,
621 : .cleanup = &post_transfers_cleanup,
622 : .traits = &post_transfers_traits
623 : };
624 :
625 0 : return cmd;
626 : }
627 : }
628 :
629 :
630 : void
631 0 : TALER_TESTING_cmd_merchant_post_transfer_set_serial (
632 : struct TALER_TESTING_Command *cmd,
633 : uint64_t serial)
634 : {
635 0 : struct PostTransfersState *pts = cmd->cls;
636 :
637 0 : GNUNET_assert (cmd->run = &post_transfers_run);
638 0 : pts->serial = serial;
639 0 : }
640 :
641 :
642 : /* end of testing_api_cmd_post_transfers.c */
|