/*
This file is part of GNU Taler
(C) 2021-2023 Taler Systems SA
GNU Taler is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation; either version 3,
or (at your option) any later version.
GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public
License along with TALER; see the file COPYING. If not,
see
*/
/**
* @file taler-merchant-httpd_private-get-instances-ID-kyc.c
* @brief implementing GET /instances/$ID/kyc request handling
* @author Christian Grothoff
*/
#include "platform.h"
#include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_exchanges.h"
#include
/**
* We do not re-check an acceptable KYC status for
* a month, as usually a KYC never expires.
*/
#define STALE_KYC_TIMEOUT GNUNET_TIME_UNIT_MONTHS
/**
* How long should clients cache a KYC failure response?
*/
#define EXPIRATION_KYC_FAILURE GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 5)
/**
* How long should clients cache a KYC success response?
*/
#define EXPIRATION_KYC_SUCCESS GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_HOURS, 1)
/**
* Information we keep per /kyc request.
*/
struct KycContext;
/**
* Structure for tracking requests to the exchange's
* ``/kyc-check`` API.
*/
struct ExchangeKycRequest
{
/**
* Kept in a DLL.
*/
struct ExchangeKycRequest *next;
/**
* Kept in a DLL.
*/
struct ExchangeKycRequest *prev;
/**
* Find operation where we connect to the respective exchange.
*/
struct TMH_EXCHANGES_KeysOperation *fo;
/**
* KYC request this exchange request is made for.
*/
struct KycContext *kc;
/**
* Hash of the wire account (with salt) we are checking.
*/
struct TALER_MerchantWireHashP h_wire;
/**
* Handle for the actual HTTP request to the exchange.
*/
struct TALER_EXCHANGE_KycCheckHandle *kyc;
/**
* KYC number used by the exchange.
*/
uint64_t exchange_kyc_serial;
/**
* Our account's payto URI.
*/
char *payto_uri;
/**
* Base URL of the exchange.
*/
char *exchange_url;
/**
* Timestamp when we last got a reply from the exchange.
*/
struct GNUNET_TIME_Timestamp last_check;
};
/**
* Information we keep per /kyc request.
*/
struct KycContext
{
/**
* Stored in a DLL.
*/
struct KycContext *next;
/**
* Stored in a DLL.
*/
struct KycContext *prev;
/**
* Connection we are handling.
*/
struct MHD_Connection *connection;
/**
* Instance we are serving.
*/
struct TMH_MerchantInstance *mi;
/**
* Our handler context.
*/
struct TMH_HandlerContext *hc;
/**
* Task to trigger on request timeout, or NULL.
*/
struct GNUNET_SCHEDULER_Task *timeout_task;
/**
* Response to return, NULL if we don't have one yet.
*/
struct MHD_Response *response;
/**
* JSON array where we are building up the array with
* pending KYC operations.
*/
json_t *pending_kycs;
/**
* JSON array where we are building up the array with
* troubled KYC operations.
*/
json_t *timeout_kycs;
/**
* Head of DLL of requests we are making to an
* exchange to inquire about the latest KYC status.
*/
struct ExchangeKycRequest *exchange_pending_head;
/**
* Tail of DLL of requests we are making to an
* exchange to inquire about the latest KYC status.
*/
struct ExchangeKycRequest *exchange_pending_tail;
/**
* Set to the exchange URL, or NULL to not filter by
* exchange.
*/
const char *exchange_url;
/**
* Set to the h_wire of the merchant account if
* @a have_h_wire is true, used to filter by account.
*/
struct TALER_MerchantWireHashP h_wire;
/**
* How long are we willing to wait for the exchange(s)?
*/
struct GNUNET_TIME_Absolute timeout;
/**
* HTTP status code to use for the reply, i.e 200 for "OK".
* Special value UINT_MAX is used to indicate hard errors
* (no reply, return #MHD_NO).
*/
unsigned int response_code;
/**
* #GNUNET_NO if the @e connection was not suspended,
* #GNUNET_YES if the @e connection was suspended,
* #GNUNET_SYSERR if @e connection was resumed to as
* part of #MH_force_pc_resume during shutdown.
*/
enum GNUNET_GenericReturnValue suspended;
/**
* True if @e h_wire was given.
*/
bool have_h_wire;
/**
* We're still waiting on the exchange to determine
* the KYC status of our deposit(s).
*/
bool kyc_serial_pending;
};
/**
* Head of DLL.
*/
static struct KycContext *kc_head;
/**
* Tail of DLL.
*/
static struct KycContext *kc_tail;
void
TMH_force_kyc_resume ()
{
for (struct KycContext *kc = kc_head;
NULL != kc;
kc = kc->next)
{
if (NULL != kc->timeout_task)
{
GNUNET_SCHEDULER_cancel (kc->timeout_task);
kc->timeout_task = NULL;
}
if (GNUNET_YES == kc->suspended)
{
kc->suspended = GNUNET_SYSERR;
MHD_resume_connection (kc->connection);
}
}
}
/**
* Custom cleanup routine for a `struct KycContext`.
*
* @param cls the `struct KycContext` to clean up.
*/
static void
kyc_context_cleanup (void *cls)
{
struct KycContext *kc = cls;
struct ExchangeKycRequest *ekr;
while (NULL != (ekr = kc->exchange_pending_head))
{
GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
kc->exchange_pending_tail,
ekr);
if (NULL != ekr->kyc)
{
TALER_EXCHANGE_kyc_check_cancel (ekr->kyc);
ekr->kyc = NULL;
}
if (NULL != ekr->fo)
{
TMH_EXCHANGES_keys4exchange_cancel (ekr->fo);
ekr->fo = NULL;
}
GNUNET_free (ekr->exchange_url);
GNUNET_free (ekr->payto_uri);
GNUNET_free (ekr);
}
if (NULL != kc->timeout_task)
{
GNUNET_SCHEDULER_cancel (kc->timeout_task);
kc->timeout_task = NULL;
}
if (NULL != kc->response)
{
MHD_destroy_response (kc->response);
kc->response = NULL;
}
GNUNET_CONTAINER_DLL_remove (kc_head,
kc_tail,
kc);
json_decref (kc->pending_kycs);
json_decref (kc->timeout_kycs);
GNUNET_free (kc);
}
/**
* Resume the given KYC context and send the given response. Stores the
* response in the @a kc and signals MHD to resume the connection. Also
* ensures MHD runs immediately.
*
* @param kc KYC context
* @param response_code response code to use
* @param response response data to send back
*/
static void
resume_kyc_with_response (struct KycContext *kc,
unsigned int response_code,
struct MHD_Response *response)
{
char dat[128];
kc->response_code = response_code;
kc->response = response;
switch (response_code)
{
case MHD_HTTP_OK:
/* KYC failed, cache briefly */
TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute (
EXPIRATION_KYC_FAILURE),
dat);
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_EXPIRES,
dat));
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_CACHE_CONTROL,
"max-age=300"));
break;
case MHD_HTTP_NO_CONTENT:
/* KYC passed, cache for a long time! */
TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute (
EXPIRATION_KYC_SUCCESS),
dat);
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_EXPIRES,
dat));
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_CACHE_CONTROL,
"max-age=3600"));
break;
case MHD_HTTP_BAD_GATEWAY:
case MHD_HTTP_GATEWAY_TIMEOUT:
break; /* no caching */
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming /kyc handling as exchange interaction is done (%u)\n",
response_code);
if (NULL != kc->timeout_task)
{
GNUNET_SCHEDULER_cancel (kc->timeout_task);
kc->timeout_task = NULL;
}
GNUNET_assert (GNUNET_YES == kc->suspended);
kc->suspended = GNUNET_NO;
MHD_resume_connection (kc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
/**
* Handle a timeout for the processing of the kyc request.
*
* @param cls our `struct KycContext`
*/
static void
handle_kyc_timeout (void *cls)
{
struct KycContext *kc = cls;
struct ExchangeKycRequest *ekr;
kc->timeout_task = NULL;
while (NULL != (ekr = kc->exchange_pending_head))
{
GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
kc->exchange_pending_tail,
ekr);
if (NULL != ekr->kyc)
{
TALER_EXCHANGE_kyc_check_cancel (ekr->kyc);
ekr->kyc = NULL;
}
if (NULL != ekr->fo)
{
TMH_EXCHANGES_keys4exchange_cancel (ekr->fo);
ekr->fo = NULL;
}
GNUNET_assert (
0 ==
json_array_append_new (
kc->timeout_kycs,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("exchange_url",
ekr->exchange_url),
GNUNET_JSON_pack_uint64 ("exchange_code",
TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT),
GNUNET_JSON_pack_uint64 ("exchange_http_status",
0))));
GNUNET_free (ekr->exchange_url);
GNUNET_free (ekr->payto_uri);
GNUNET_free (ekr);
}
GNUNET_assert (GNUNET_YES == kc->suspended);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming KYC with gateway timeout\n");
resume_kyc_with_response (
kc,
MHD_HTTP_GATEWAY_TIMEOUT,
TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_incref ("pending_kycs",
kc->pending_kycs),
GNUNET_JSON_pack_array_incref ("timeout_kycs",
kc->timeout_kycs)));
}
/**
* We are done with the KYC request @a ekr. Remove it from the work list and
* check if we are done overall.
*
* @param[in] ekr key request that is done (and will be freed)
*/
static void
ekr_finished (struct ExchangeKycRequest *ekr)
{
struct KycContext *kc = ekr->kc;
GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
kc->exchange_pending_tail,
ekr);
GNUNET_free (ekr->exchange_url);
GNUNET_free (ekr->payto_uri);
GNUNET_free (ekr);
if (NULL != kc->exchange_pending_head)
return; /* wait for more */
/* All exchange requests done, create final
big response from cumulated replies */
if ( (0 == json_array_size (kc->pending_kycs)) &&
(0 == json_array_size (kc->timeout_kycs)) )
{
/* special case: all KYC operations did succeed
after we asked at the exchanges => 204 */
struct MHD_Response *response;
response = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
resume_kyc_with_response (kc,
MHD_HTTP_NO_CONTENT,
response);
return;
}
resume_kyc_with_response (
kc,
kc->response_code, /* MHD_HTTP_OK or MHD_HTTP_BAD_GATEWAY */
TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_array_incref ("pending_kycs",
kc->pending_kycs),
GNUNET_JSON_pack_array_incref ("timeout_kycs",
kc->timeout_kycs)));
}
/**
* Function called with the result of a KYC check.
*
* @param cls a `struct ExchangeKycRequest *`
* @param ks the account's KYC status details
*/
static void
exchange_check_cb (void *cls,
const struct TALER_EXCHANGE_KycStatus *ks)
{
struct ExchangeKycRequest *ekr = cls;
struct KycContext *kc = ekr->kc;
ekr->kyc = NULL;
switch (ks->http_status)
{
case MHD_HTTP_OK:
{
enum GNUNET_DB_QueryStatus qs;
if (TALER_AML_NORMAL != ks->details.ok.aml_status)
{
GNUNET_assert (
0 ==
json_array_append_new (
kc->pending_kycs,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 (
"aml_status",
ks->details.ok.aml_status),
GNUNET_JSON_pack_string ("exchange_url",
ekr->exchange_url),
GNUNET_JSON_pack_string ("payto_uri",
ekr->payto_uri))));
}
qs = TMH_db->account_kyc_set_status (TMH_db->cls,
kc->mi->settings.id,
&ekr->h_wire,
ekr->exchange_url,
ekr->exchange_kyc_serial,
&ks->details.ok.exchange_sig,
&ks->details.ok.exchange_pub,
ks->details.ok.timestamp,
true, /* KYC OK */
ks->details.ok.aml_status);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to store KYC status in database!\n");
}
}
break;
case MHD_HTTP_ACCEPTED:
{
struct GNUNET_TIME_Timestamp now;
enum GNUNET_DB_QueryStatus qs;
GNUNET_assert (
0 ==
json_array_append_new (
kc->pending_kycs,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("kyc_url",
ks->details.accepted.kyc_url),
GNUNET_JSON_pack_uint64 ("aml_status",
ks->details.accepted.aml_status),
GNUNET_JSON_pack_string ("exchange_url",
ekr->exchange_url),
GNUNET_JSON_pack_string ("payto_uri",
ekr->payto_uri))));
now = GNUNET_TIME_timestamp_get ();
qs = TMH_db->account_kyc_set_status (
TMH_db->cls,
kc->mi->settings.id,
&ekr->h_wire,
ekr->exchange_url,
ekr->exchange_kyc_serial,
NULL,
NULL,
now,
false, /* KYC not OK */
ks->details.accepted.aml_status);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to store KYC status in database!\n");
}
break;
}
case MHD_HTTP_NO_CONTENT:
{
struct GNUNET_TIME_Timestamp now;
enum GNUNET_DB_QueryStatus qs;
now = GNUNET_TIME_timestamp_get ();
qs = TMH_db->account_kyc_set_status (TMH_db->cls,
kc->mi->settings.id,
&ekr->h_wire,
ekr->exchange_url,
ekr->exchange_kyc_serial,
NULL,
NULL,
now,
true, /* KYC OK */
TALER_AML_NORMAL);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to store KYC status in database!\n");
}
}
break;
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
{
struct GNUNET_TIME_Timestamp now;
enum GNUNET_DB_QueryStatus qs;
GNUNET_assert (
0 ==
json_array_append_new (
kc->pending_kycs,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 (
"aml_status",
ks->details.unavailable_for_legal_reasons.aml_status),
GNUNET_JSON_pack_string ("exchange_url",
ekr->exchange_url),
GNUNET_JSON_pack_string ("payto_uri",
ekr->payto_uri))));
now = GNUNET_TIME_timestamp_get ();
qs = TMH_db->account_kyc_set_status (
TMH_db->cls,
kc->mi->settings.id,
&ekr->h_wire,
ekr->exchange_url,
ekr->exchange_kyc_serial,
NULL,
NULL,
now,
true, /* KYC is OK, AML not... */
ks->details.unavailable_for_legal_reasons.aml_status);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to store KYC status in database!\n");
}
}
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Exchange responded with HTTP status %u (%d) to /kyc-check request!\n",
ks->http_status,
ks->ec);
kc->response_code = MHD_HTTP_BAD_GATEWAY;
GNUNET_assert (
0 ==
json_array_append_new (
kc->timeout_kycs,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("exchange_url",
ekr->exchange_url),
GNUNET_JSON_pack_uint64 ("exchange_code",
ks->ec),
GNUNET_JSON_pack_uint64 ("exchange_http_status",
ks->http_status))));
}
ekr_finished (ekr);
}
/**
* Function called with the result of a #TMH_EXCHANGES_keys4exchange()
* operation. Runs the KYC check against the exchange.
*
* @param cls closure with our `struct ExchangeKycRequest *`
* @param keys keys of the exchange context
* @param exchange representation of the exchange
*/
static void
kyc_with_exchange (void *cls,
struct TALER_EXCHANGE_Keys *keys,
struct TMH_Exchange *exchange)
{
struct ExchangeKycRequest *ekr = cls;
struct KycContext *kc = ekr->kc;
struct TALER_PaytoHashP h_payto;
(void) exchange;
ekr->fo = NULL;
if (NULL == keys)
{
kc->response_code = MHD_HTTP_BAD_GATEWAY;
GNUNET_assert (
0 ==
json_array_append_new (
kc->timeout_kycs,
GNUNET_JSON_PACK (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE))));
ekr_finished (ekr);
return;
}
TALER_payto_hash (ekr->payto_uri,
&h_payto);
ekr->kyc = TALER_EXCHANGE_kyc_check (
TMH_curl_ctx,
ekr->exchange_url,
keys,
ekr->exchange_kyc_serial,
&h_payto,
ekr->kc->mi->settings.ut,
GNUNET_TIME_absolute_get_remaining (kc->timeout),
&exchange_check_cb,
ekr);
}
/**
* Function called from account_kyc_get_status() with KYC status information
* for this merchant.
*
* @param cls our `struct KycContext *`
* @param h_wire hash of the wire account
* @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown
* @param payto_uri payto:// URI of the merchant's bank account
* @param exchange_url base URL of the exchange for which this is a status
* @param last_check when did we last get an update on our KYC status from the exchange
* @param kyc_ok true if we satisfied the KYC requirements
* @param aml_decision latest AML decision known to us
*/
static void
kyc_status_cb (void *cls,
const struct TALER_MerchantWireHashP *h_wire,
uint64_t exchange_kyc_serial,
const char *payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
bool kyc_ok,
enum TALER_AmlDecisionState aml_decision)
{
struct KycContext *kc = cls;
struct ExchangeKycRequest *ekr;
if (kyc_ok &&
(TALER_AML_PENDING != aml_decision) &&
(GNUNET_TIME_relative_cmp (
GNUNET_TIME_absolute_get_duration (last_check.abs_time),
<,
STALE_KYC_TIMEOUT)) )
return; /* KYC ok, ignore! */
if (0 == exchange_kyc_serial)
{
kc->kyc_serial_pending = true;
return;
}
kc->response_code = MHD_HTTP_ACCEPTED;
ekr = GNUNET_new (struct ExchangeKycRequest);
GNUNET_CONTAINER_DLL_insert (kc->exchange_pending_head,
kc->exchange_pending_tail,
ekr);
ekr->h_wire = *h_wire;
ekr->exchange_kyc_serial = exchange_kyc_serial;
ekr->exchange_url = GNUNET_strdup (exchange_url);
ekr->payto_uri = GNUNET_strdup (payto_uri);
ekr->last_check = last_check;
ekr->kc = kc;
ekr->fo = TMH_EXCHANGES_keys4exchange (exchange_url,
false,
&kyc_with_exchange,
ekr);
}
/**
* Check the KYC status of an instance.
*
* @param mi instance to check KYC status of
* @param connection the MHD connection to handle
* @param[in,out] hc context with further information about the request
* @return MHD result code
*/
static MHD_RESULT
get_instances_ID_kyc (struct TMH_MerchantInstance *mi,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct KycContext *kc = hc->ctx;
if (NULL == kc)
{
kc = GNUNET_new (struct KycContext);
kc->mi = mi;
hc->ctx = kc;
hc->cc = &kyc_context_cleanup;
GNUNET_CONTAINER_DLL_insert (kc_head,
kc_tail,
kc);
kc->connection = connection;
kc->hc = hc;
kc->pending_kycs = json_array ();
GNUNET_assert (NULL != kc->pending_kycs);
kc->timeout_kycs = json_array ();
GNUNET_assert (NULL != kc->timeout_kycs);
TALER_MHD_parse_request_timeout (connection,
&kc->timeout);
if (! GNUNET_TIME_absolute_is_past (kc->timeout))
kc->timeout_task
= GNUNET_SCHEDULER_add_at (kc->timeout,
&handle_kyc_timeout,
kc);
/* process 'exchange_url' argument */
kc->exchange_url = MHD_lookup_connection_value (connection,
MHD_GET_ARGUMENT_KIND,
"exchange_url");
if ( (NULL != kc->exchange_url) &&
(! TALER_url_valid_charset (kc->exchange_url) ||
( (0 != strncasecmp (kc->exchange_url,
"http://",
strlen ("http://"))) &&
(0 != strncasecmp (kc->exchange_url,
"https://",
strlen ("https://"))) ) ) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"exchange_url must be a valid HTTP(s) URL");
}
TALER_MHD_parse_request_arg_auto (connection,
"h_wire",
&kc->h_wire,
kc->have_h_wire);
/* Check our database */
{
enum GNUNET_DB_QueryStatus qs;
qs = TMH_db->account_kyc_get_status (TMH_db->cls,
mi->settings.id,
kc->have_h_wire
? &kc->h_wire
: NULL,
kc->exchange_url,
&kyc_status_cb,
kc);
if (qs < 0)
{
GNUNET_break (0);
return TALER_MHD_reply_with_ec (connection,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"account_kyc_get_status");
}
}
if (kc->kyc_serial_pending)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange legitimization UUID unknown, assuming KYC pending\n");
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_SERVICE_UNAVAILABLE,
GNUNET_JSON_pack_string ("hint",
"awaiting legitimization UUID"));
}
if (NULL == kc->exchange_pending_head)
return TALER_MHD_reply_static (connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
MHD_suspend_connection (connection);
kc->suspended = GNUNET_YES;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Suspending KYC request handling while checking with the exchange(s)\n");
return MHD_YES;
}
if (GNUNET_SYSERR == kc->suspended)
return MHD_NO; /* during shutdown, we don't generate any more replies */
GNUNET_assert (GNUNET_NO == kc->suspended);
if (0 != kc->response_code)
{
/* We are *done* processing the request, just queue the response (!) */
if (UINT_MAX == kc->response_code)
{
GNUNET_break (0);
return MHD_NO; /* hard error */
}
return MHD_queue_response (connection,
kc->response_code,
kc->response);
}
/* we should never get here */
GNUNET_break (0);
return MHD_NO;
}
MHD_RESULT
TMH_private_get_instances_ID_kyc (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
(void) rh;
return get_instances_ID_kyc (mi,
connection,
hc);
}
MHD_RESULT
TMH_private_get_instances_default_ID_kyc (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi;
(void) rh;
mi = TMH_lookup_instance (hc->infix);
if (NULL == mi)
{
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
hc->infix);
}
return get_instances_ID_kyc (mi,
connection,
hc);
}
/* end of taler-merchant-httpd_private-get-instances-ID-kyc.c */