/*
This file is part of TALER
Copyright (C) 2023 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 pends pending_kycs array from the reply
* @param touts timeout_kycs 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 *pends,
const json_t *touts)
{
unsigned int num_pends = (unsigned int) json_array_size (pends);
unsigned int num_touts = (unsigned int) json_array_size (touts);
if ( (json_array_size (pends) != (size_t) num_pends) ||
(num_pends > MAX_KYC) )
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if ( (json_array_size (touts) != (size_t) num_touts) ||
(num_touts > MAX_KYC) )
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
{
struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[
GNUNET_NZL (num_pends)];
struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[
GNUNET_NZL (num_touts)];
memset (pending_kycs,
0,
sizeof (pending_kycs));
for (unsigned int i = 0; idetails.kyc_status.pending_kycs = pending_kycs;
kr->details.kyc_status.timeout_kycs = timeout_kycs;
kr->details.kyc_status.pending_kycs_length = num_pends;
kr->details.kyc_status.timeout_kycs_length = num_touts;
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_NO_CONTENT:
break;
case MHD_HTTP_ACCEPTED:
case MHD_HTTP_BAD_GATEWAY:
case MHD_HTTP_GATEWAY_TIMEOUT:
{
const json_t *pends;
const json_t *touts;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("pending_kycs",
&pends),
GNUNET_JSON_spec_array_const ("timeout_kycs",
&touts),
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,
pends,
touts))
{
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_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 timeout how long to wait for a (positive) 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,
struct GNUNET_TIME_Relative timeout,
TALER_MERCHANT_KycGetCallback cb,
void *cb_cls)
{
struct TALER_MERCHANT_KycGetHandle *kyc;
CURL *eh;
char timeout_ms[32];
unsigned int tms;
kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle);
kyc->ctx = ctx;
kyc->cb = cb;
kyc->cb_cls = cb_cls;
tms = (unsigned int) (timeout.rel_value_us
/ GNUNET_TIME_UNIT_MILLISECONDS.
rel_value_us);
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
"%u",
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,
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,
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,
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,
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,
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);
}