/* This file is part of TALER Copyright (C) 2014-2017, 2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TALER; see the file COPYING.LGPL. If not, see */ /** * @file lib/merchant_api_get_transfers.c * @brief Implementation of the GET /transfers request of the merchant's HTTP API * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include /* just for HTTP status codes */ #include #include #include "taler_merchant_service.h" #include #include /** * @brief A Handle for tracking wire transfers. */ struct TALER_MERCHANT_GetTransfersHandle { /** * The url for this request. */ char *url; /** * Handle for the request. */ struct GNUNET_CURL_Job *job; /** * Function to call with the result. */ TALER_MERCHANT_GetTransfersCallback cb; /** * Closure for @a cb. */ void *cb_cls; /** * Reference to the execution context. */ struct GNUNET_CURL_Context *ctx; }; /** * Function called when we're done processing the * HTTP GET /transfers request. * * @param cls the `struct TALER_MERCHANT_GetTransfersHandle` * @param response_code HTTP response code, 0 on error * @param json response body, NULL if not in JSON */ static void handle_transfers_get_finished (void *cls, long response_code, const void *response) { struct TALER_MERCHANT_GetTransfersHandle *gth = cls; const json_t *json = response; struct TALER_MERCHANT_HttpResponse hr = { .http_status = (unsigned int) response_code, .reply = json }; gth->job = NULL; switch (response_code) { case 0: hr.ec = TALER_EC_INVALID_RESPONSE; break; case MHD_HTTP_OK: { json_t *transfers; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_json ("transfers", &transfers), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (json, spec, NULL, NULL)) { GNUNET_break_op (0); hr.http_status = 0; hr.ec = TALER_EC_INVALID_RESPONSE; break; } else { size_t tds_length; struct TALER_MERCHANT_TransferData *tds; json_t *transfer; unsigned int i; bool ok; if (! json_is_array (transfers)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); hr.http_status = 0; hr.ec = TALER_EC_INVALID_RESPONSE; break; } tds_length = json_array_size (transfers); tds = GNUNET_new_array (tds_length, struct TALER_MERCHANT_TransferData); ok = true; json_array_foreach (transfers, i, transfer) { /* FIXME: handle 'execution_time', 'verified', and/or 'confirmed' not present in the response. */ struct TALER_MERCHANT_TransferData *td = &tds[i]; struct GNUNET_JSON_Specification ispec[] = { TALER_JSON_spec_amount ("credit_amount", &td->credit_amount), GNUNET_JSON_spec_fixed_auto ("wtid", &td->wtid), GNUNET_JSON_spec_string ("payto_uri", &td->payto_uri), GNUNET_JSON_spec_string ("exchange_url", &td->exchange_url), GNUNET_JSON_spec_uint64 ("transfer_serial_id", &td->credit_serial), TALER_JSON_spec_absolute_time ("execution_time", &td->execution_time), GNUNET_JSON_spec_bool ("verified", &td->verified), GNUNET_JSON_spec_bool ("confirmed", &td->confirmed), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (transfer, ispec, NULL, NULL)) { GNUNET_break_op (0); ok = false; break; } } if (! ok) { GNUNET_break_op (0); GNUNET_free (tds); GNUNET_JSON_parse_free (spec); hr.http_status = 0; hr.ec = TALER_EC_INVALID_RESPONSE; break; } gth->cb (gth->cb_cls, &hr, tds_length, tds); GNUNET_free (tds); GNUNET_JSON_parse_free (spec); TALER_MERCHANT_transfers_get_cancel (gth); return; } } case MHD_HTTP_NOT_FOUND: /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ hr.ec = TALER_JSON_get_error_code (json); hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ hr.ec = TALER_JSON_get_error_code (json); hr.hint = TALER_JSON_get_error_hint (json); break; default: /* unexpected response code */ GNUNET_break_op (0); TALER_MERCHANT_parse_error_details_ (json, response_code, &hr); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, (int) hr.ec); response_code = 0; break; } gth->cb (gth->cb_cls, &hr, 0, NULL); TALER_MERCHANT_transfers_get_cancel (gth); } /** * Request backend to return list of all wire transfers that * we received (or that the exchange claims we should have received). * * Note that when filtering by timestamp (using “before” and/or “after”), we * use the time reported by the exchange and thus will ONLY return results for * which we already have a response from the exchange. This should be * virtually all transfers, however it is conceivable that for some transfer * the exchange responded with a temporary error (i.e. HTTP status 500+) and * then we do not yet have an execution time to filter by. Thus, IF timestamp * filters are given, transfers for which we have no response from the * exchange yet are automatically excluded. * * @param ctx execution context * @param backend_url base URL of the backend * @param payto_uri filter by this credit account of the merchant * @param before limit to transactions before this timestamp, use * #GNUNET_TIME_UNIT_FOREVER_ABS to not filter by @a before * @param after limit to transactions after this timestamp, use * #GNUNET_TIME_UNIT_ZERO_ABS to not filter by @a after * @param limit return at most this number of results; negative to descend in execution time * @param offset start at this "credit serial" number (exclusive) * @param verified filter results by verification status * @param cb the callback to call when a reply for this request is available * @param cb_cls closure for @a cb * @return a handle for this request */ struct TALER_MERCHANT_GetTransfersHandle * TALER_MERCHANT_transfers_get ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const char *payto_uri, const struct GNUNET_TIME_Absolute before, const struct GNUNET_TIME_Absolute after, int64_t limit, uint64_t offset, enum TALER_EXCHANGE_YesNoAll verified, TALER_MERCHANT_GetTransfersCallback cb, void *cb_cls) { struct TALER_MERCHANT_GetTransfersHandle *gth; CURL *eh; const char *verified_s = NULL; char limit_s[30]; char offset_s[30]; char *before_s; char *after_s; gth = GNUNET_new (struct TALER_MERCHANT_GetTransfersHandle); gth->ctx = ctx; gth->cb = cb; gth->cb_cls = cb_cls; verified_s = TALER_yna_to_string (verified); GNUNET_snprintf (limit_s, sizeof (limit_s), "%lld", (long long) limit); GNUNET_snprintf (offset_s, sizeof (offset_s), "%lld", (unsigned long long) offset); before_s = GNUNET_strdup (GNUNET_STRINGS_absolute_time_to_string (before)); after_s = GNUNET_strdup (GNUNET_STRINGS_absolute_time_to_string (after)); gth->url = TALER_url_join (backend_url, "private/transfers", "payto_uri", payto_uri, "verified", (TALER_EXCHANGE_YNA_ALL != verified) ? verified_s : NULL, "limit", 0 != limit ? limit_s : NULL, "offset", ((0 != offset) && (UINT64_MAX != offset)) ? offset_s : NULL, "before", before.abs_value_us != GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us ? before_s : NULL, "after", after.abs_value_us != 0 ? after_s : NULL, NULL); GNUNET_free (before_s); GNUNET_free (after_s); if (NULL == gth->url) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not construct request URL.\n"); GNUNET_free (gth); return NULL; } eh = curl_easy_init (); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_URL, gth->url)); gth->job = GNUNET_CURL_job_add (ctx, eh, &handle_transfers_get_finished, gth); return gth; } /** * Cancel a GET /transfers request. This function cannot be used * on a request handle if a response is already served for it. * * @param gth handle to the tracking operation being cancelled */ void TALER_MERCHANT_transfers_get_cancel ( struct TALER_MERCHANT_GetTransfersHandle *gth) { if (NULL != gth->job) { GNUNET_CURL_job_cancel (gth->job); gth->job = NULL; } GNUNET_free (gth->url); GNUNET_free (gth); } /* end of merchant_api_get_transfers.c */