/*
This file is part of TALER
Copyright (C) 2023--2024 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 merchant_api_get_kyc.c
* @brief Implementation of the GET /kyc request of the merchant's HTTP API
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include /* just for HTTP status codes */
#include
#include
#include "taler_merchant_service.h"
#include "merchant_api_curl_defaults.h"
#include
#include
/**
* Maximum length of the KYC arrays supported.
*/
#define MAX_KYC 1024
/**
* Handle for a GET /kyc operation.
*/
struct TALER_MERCHANT_KycGetHandle
{
/**
* The url for this request.
*/
char *url;
/**
* Handle for the request.
*/
struct GNUNET_CURL_Job *job;
/**
* Function to call with the result.
*/
TALER_MERCHANT_KycGetCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Reference to the execution context.
*/
struct GNUNET_CURL_Context *ctx;
};
/**
* Parse @a kyc response and call the continuation on success.
*
* @param kyc operation handle
* @param[in,out] kr response details
* @param jkyc array from the reply
* @return #GNUNET_OK on success (callback was called)
*/
static enum GNUNET_GenericReturnValue
parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc,
struct TALER_MERCHANT_KycResponse *kr,
const json_t *jkyc)
{
unsigned int num_kycs = (unsigned int) json_array_size (jkyc);
unsigned int num_limits = 0;
unsigned int num_kycauths = 0;
unsigned int pos_limits = 0;
unsigned int pos_kycauths = 0;
if ( (json_array_size (jkyc) != (size_t) num_kycs) ||
(num_kycs > MAX_KYC) )
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
for (unsigned int i = 0; ipayto_uri),
TALER_JSON_spec_web_url (
"exchange_url",
&rd->exchange_url),
GNUNET_JSON_spec_uint32 (
"exchange_http_status",
&hs),
GNUNET_JSON_spec_bool (
"no_keys",
&rd->no_keys),
GNUNET_JSON_spec_bool (
"auth_conflict",
&rd->auth_conflict),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_ec (
"exchange_code",
&rd->exchange_code),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto (
"access_token",
&rd->access_token),
&rd->no_access_token),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const (
"limits",
&jlimits),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const (
"payto_kycauths",
&jkycauths),
NULL),
GNUNET_JSON_spec_end ()
};
size_t j;
json_t *jlimit;
json_t *jkycauth;
if (GNUNET_OK !=
GNUNET_JSON_parse (json_array_get (jkyc,
i),
spec,
NULL, NULL))
{
GNUNET_break (0);
json_dumpf (json_array_get (jkyc,
i),
stderr,
JSON_INDENT (2));
return GNUNET_SYSERR;
}
rd->exchange_http_status = (unsigned int) hs;
rd->limits = &limits[pos_limits];
rd->limits_length = json_array_size (jlimits);
json_array_foreach (jlimits, j, jlimit)
{
struct TALER_EXCHANGE_AccountLimit *limit
= &limits[pos_limits];
struct GNUNET_JSON_Specification jspec[] = {
TALER_JSON_spec_kycte (
"operation_type",
&limit->operation_type),
GNUNET_JSON_spec_relative_time (
"timeframe",
&limit->timeframe),
TALER_JSON_spec_amount_any (
"threshold",
&limit->threshold),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool (
"soft_limit",
&limit->soft_limit),
NULL),
GNUNET_JSON_spec_end ()
};
GNUNET_assert (pos_limits < num_limits);
limit->soft_limit = false;
if (GNUNET_OK !=
GNUNET_JSON_parse (jlimit,
jspec,
NULL, NULL))
{
GNUNET_break (0);
json_dumpf (json_array_get (jkyc,
i),
stderr,
JSON_INDENT (2));
return GNUNET_SYSERR;
}
pos_limits++;
}
rd->payto_kycauths = &payto_kycauths[pos_kycauths];
rd->pkycauth_length = json_array_size (jkycauths);
json_array_foreach (jkycauths, j, jkycauth)
{
GNUNET_assert (pos_kycauths < num_kycauths);
payto_kycauths[pos_kycauths].full_payto
= (char *) json_string_value (jkycauth);
if (NULL == payto_kycauths[pos_kycauths].full_payto)
{
GNUNET_break (0);
json_dumpf (json_array_get (jkyc,
i),
stderr,
JSON_INDENT (2));
return GNUNET_SYSERR;
}
pos_kycauths++;
}
}
kr->details.ok.kycs = kycs;
kr->details.ok.kycs_length = num_kycs;
kyc->cb (kyc->cb_cls,
kr);
}
return GNUNET_OK;
}
/**
* Function called when we're done processing the
* HTTP /kyc request.
*
* @param cls the `struct TALER_MERCHANT_KycGetHandle`
* @param response_code HTTP response code, 0 on error
* @param response response body, NULL if not in JSON
*/
static void
handle_get_kyc_finished (void *cls,
long response_code,
const void *response)
{
struct TALER_MERCHANT_KycGetHandle *kyc = cls;
const json_t *json = response;
struct TALER_MERCHANT_KycResponse kr = {
.hr.http_status = (unsigned int) response_code,
.hr.reply = json
};
kyc->job = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Got /kyc response with status code %u\n",
(unsigned int) response_code);
switch (response_code)
{
case MHD_HTTP_OK:
{
const json_t *jkyc;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("kyc_data",
&jkyc),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
spec,
NULL, NULL))
{
kr.hr.http_status = 0;
kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
if (GNUNET_OK !=
parse_kyc (kyc,
&kr,
jkyc))
{
kr.hr.http_status = 0;
kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
/* parse_kyc called the continuation already */
TALER_MERCHANT_kyc_get_cancel (kyc);
return;
}
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_UNAUTHORIZED:
kr.hr.ec = TALER_JSON_get_error_code (json);
kr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_SERVICE_UNAVAILABLE:
break;
default:
/* unexpected response code */
kr.hr.ec = TALER_JSON_get_error_code (json);
kr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
(int) kr.hr.ec);
break;
}
kyc->cb (kyc->cb_cls,
&kr);
TALER_MERCHANT_kyc_get_cancel (kyc);
}
/**
* Issue a GET KYC request to the backend.
* Returns KYC status of bank accounts.
*
* @param ctx execution context
* @param[in] url URL to use for the request, consumed!
* @param h_wire which bank account to query, NULL for all
* @param exchange_url which exchange to query, NULL for all
* @param lpt target for long polling
* @param timeout how long to wait for a reply
* @param cb function to call with the result
* @param cb_cls closure for @a cb
* @return handle for this operation, NULL upon errors
*/
static struct TALER_MERCHANT_KycGetHandle *
kyc_get (struct GNUNET_CURL_Context *ctx,
char *url,
const struct TALER_MerchantWireHashP *h_wire,
const char *exchange_url,
enum TALER_EXCHANGE_KycLongPollTarget lpt,
struct GNUNET_TIME_Relative timeout,
TALER_MERCHANT_KycGetCallback cb,
void *cb_cls)
{
struct TALER_MERCHANT_KycGetHandle *kyc;
CURL *eh;
char timeout_ms[32];
char lpt_str[32];
unsigned long long tms;
kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle);
kyc->ctx = ctx;
kyc->cb = cb;
kyc->cb_cls = cb_cls;
GNUNET_snprintf (lpt_str,
sizeof (lpt_str),
"%d",
(int) lpt);
tms = timeout.rel_value_us
/ GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
"%llu",
tms);
kyc->url
= TALER_url_join (
url,
"kyc",
"h_wire",
NULL == h_wire
? NULL
: GNUNET_h2s_full (&h_wire->hash),
"exchange_url",
NULL == exchange_url
? NULL
: exchange_url,
"timeout_ms",
GNUNET_TIME_relative_is_zero (timeout)
? NULL
: timeout_ms,
"lpt",
TALER_EXCHANGE_KLPT_NONE == lpt
? NULL
: lpt_str,
NULL);
GNUNET_free (url);
if (NULL == kyc->url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not construct request URL.\n");
GNUNET_free (kyc);
return NULL;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Requesting URL '%s'\n",
kyc->url);
eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
if (0 != tms)
{
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TIMEOUT_MS,
(long) (tms + 100L)));
}
kyc->job
= GNUNET_CURL_job_add (ctx,
eh,
&handle_get_kyc_finished,
kyc);
return kyc;
}
struct TALER_MERCHANT_KycGetHandle *
TALER_MERCHANT_kyc_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const struct TALER_MerchantWireHashP *h_wire,
const char *exchange_url,
enum TALER_EXCHANGE_KycLongPollTarget lpt,
struct GNUNET_TIME_Relative timeout,
TALER_MERCHANT_KycGetCallback cb,
void *cb_cls)
{
char *url;
GNUNET_asprintf (&url,
"%sprivate/",
backend_url);
return kyc_get (ctx,
url, /* consumed! */
h_wire,
exchange_url,
lpt,
timeout,
cb,
cb_cls);
}
struct TALER_MERCHANT_KycGetHandle *
TALER_MERCHANT_management_kyc_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *instance_id,
const struct TALER_MerchantWireHashP *h_wire,
const char *exchange_url,
enum TALER_EXCHANGE_KycLongPollTarget lpt,
struct GNUNET_TIME_Relative timeout,
TALER_MERCHANT_KycGetCallback cb,
void *cb_cls)
{
char *url;
GNUNET_asprintf (&url,
"%smanagement/instances/%s/",
backend_url,
instance_id);
return kyc_get (ctx,
url, /* consumed! */
h_wire,
exchange_url,
lpt,
timeout,
cb,
cb_cls);
}
void
TALER_MERCHANT_kyc_get_cancel (
struct TALER_MERCHANT_KycGetHandle *kyc)
{
if (NULL != kyc->job)
GNUNET_CURL_job_cancel (kyc->job);
GNUNET_free (kyc->url);
GNUNET_free (kyc);
}