LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-get-instances-ID-kyc.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 234 345 67.8 %
Date: 2025-06-23 16:22:09 Functions: 13 15 86.7 %

          Line data    Source code
       1             : /*
       2             :   This file is part of GNU Taler
       3             :   (C) 2021-2024 Taler Systems SA
       4             : 
       5             :   GNU Taler is free software; you can redistribute it and/or modify
       6             :   it under the terms of the GNU Affero General Public License as
       7             :   published by the Free Software Foundation; either version 3,
       8             :   or (at your option) any later version.
       9             : 
      10             :   GNU 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,
      17             :   see <http://www.gnu.org/licenses/>
      18             : */
      19             : 
      20             : /**
      21             :  * @file taler-merchant-httpd_private-get-instances-ID-kyc.c
      22             :  * @brief implementing GET /instances/$ID/kyc request handling
      23             :  * @author Christian Grothoff
      24             :  */
      25             : #include "platform.h"
      26             : #include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
      27             : #include "taler-merchant-httpd_helper.h"
      28             : #include "taler-merchant-httpd_exchanges.h"
      29             : #include <taler/taler_json_lib.h>
      30             : #include <taler/taler_dbevents.h>
      31             : #include <regex.h>
      32             : 
      33             : /**
      34             :  * Information we keep per /kyc request.
      35             :  */
      36             : struct KycContext;
      37             : 
      38             : 
      39             : /**
      40             :  * Structure for tracking requests to the exchange's
      41             :  * ``/kyc-check`` API.
      42             :  */
      43             : struct ExchangeKycRequest
      44             : {
      45             :   /**
      46             :    * Kept in a DLL.
      47             :    */
      48             :   struct ExchangeKycRequest *next;
      49             : 
      50             :   /**
      51             :    * Kept in a DLL.
      52             :    */
      53             :   struct ExchangeKycRequest *prev;
      54             : 
      55             :   /**
      56             :    * Find operation where we connect to the respective exchange.
      57             :    */
      58             :   struct TMH_EXCHANGES_KeysOperation *fo;
      59             : 
      60             :   /**
      61             :    * JSON array of payto-URIs with KYC auth wire transfer
      62             :    * instructions.  Provided if @e auth_ok is false and
      63             :    * @e kyc_auth_conflict is false.
      64             :    */
      65             :   json_t *pkaa;
      66             : 
      67             :   /**
      68             :    * The keys of the exchange.
      69             :    */
      70             :   struct TALER_EXCHANGE_Keys *keys;
      71             : 
      72             :   /**
      73             :    * KYC request this exchange request is made for.
      74             :    */
      75             :   struct KycContext *kc;
      76             : 
      77             :   /**
      78             :    * JSON array of AccountLimits that apply, NULL if
      79             :    * unknown (and likely defaults apply).
      80             :    */
      81             :   json_t *jlimits;
      82             : 
      83             :   /**
      84             :    * Our account's payto URI.
      85             :    */
      86             :   struct TALER_FullPayto payto_uri;
      87             : 
      88             :   /**
      89             :    * Base URL of the exchange.
      90             :    */
      91             :   char *exchange_url;
      92             : 
      93             :   /**
      94             :    * Hash of the wire account (with salt) we are checking.
      95             :    */
      96             :   struct TALER_MerchantWireHashP h_wire;
      97             : 
      98             :   /**
      99             :    * Current access token for the KYC SPA. Only set
     100             :    * if @e auth_ok is true.
     101             :    */
     102             :   struct TALER_AccountAccessTokenP access_token;
     103             : 
     104             :   /**
     105             :    * Timestamp when we last got a reply from the exchange.
     106             :    */
     107             :   struct GNUNET_TIME_Timestamp last_check;
     108             : 
     109             :   /**
     110             :    * Last HTTP status code obtained via /kyc-check from
     111             :    * the exchange.
     112             :    */
     113             :   unsigned int last_http_status;
     114             : 
     115             :   /**
     116             :    * Last Taler error code returned from /kyc-check.
     117             :    */
     118             :   enum TALER_ErrorCode last_ec;
     119             : 
     120             :   /**
     121             :    * True if this account
     122             :    * cannot work at this exchange because KYC auth is
     123             :    * impossible.
     124             :    */
     125             :   bool kyc_auth_conflict;
     126             : 
     127             :   /**
     128             :    * We could not get /keys from the exchange.
     129             :    */
     130             :   bool no_keys;
     131             : 
     132             :   /**
     133             :    * True if @e access_token is available.
     134             :    */
     135             :   bool auth_ok;
     136             : 
     137             :   /**
     138             :    * True if we believe no KYC is currently required
     139             :    * for this account at this exchange.
     140             :    */
     141             :   bool kyc_ok;
     142             : 
     143             :   /**
     144             :    * True if the exchange exposed to us that the account
     145             :    * is currently under AML review.
     146             :    */
     147             :   bool in_aml_review;
     148             : 
     149             : 
     150             : };
     151             : 
     152             : 
     153             : /**
     154             :  * Information we keep per /kyc request.
     155             :  */
     156             : struct KycContext
     157             : {
     158             :   /**
     159             :    * Stored in a DLL.
     160             :    */
     161             :   struct KycContext *next;
     162             : 
     163             :   /**
     164             :    * Stored in a DLL.
     165             :    */
     166             :   struct KycContext *prev;
     167             : 
     168             :   /**
     169             :    * Connection we are handling.
     170             :    */
     171             :   struct MHD_Connection *connection;
     172             : 
     173             :   /**
     174             :    * Instance we are serving.
     175             :    */
     176             :   struct TMH_MerchantInstance *mi;
     177             : 
     178             :   /**
     179             :    * Our handler context.
     180             :    */
     181             :   struct TMH_HandlerContext *hc;
     182             : 
     183             :   /**
     184             :    * Response to return, NULL if we don't have one yet.
     185             :    */
     186             :   struct MHD_Response *response;
     187             : 
     188             :   /**
     189             :    * JSON array where we are building up the array with
     190             :    * pending KYC operations.
     191             :    */
     192             :   json_t *kycs_data;
     193             : 
     194             :   /**
     195             :    * Head of DLL of requests we are making to an
     196             :    * exchange to inquire about the latest KYC status.
     197             :    */
     198             :   struct ExchangeKycRequest *exchange_pending_head;
     199             : 
     200             :   /**
     201             :    * Tail of DLL of requests we are making to an
     202             :    * exchange to inquire about the latest KYC status.
     203             :    */
     204             :   struct ExchangeKycRequest *exchange_pending_tail;
     205             : 
     206             :   /**
     207             :    * Set to the exchange URL, or NULL to not filter by
     208             :    * exchange.
     209             :    */
     210             :   const char *exchange_url;
     211             : 
     212             :   /**
     213             :    * Notification handler from database on changes
     214             :    * to the KYC status.
     215             :    */
     216             :   struct GNUNET_DB_EventHandler *eh;
     217             : 
     218             :   /**
     219             :    * Set to the h_wire of the merchant account if
     220             :    * @a have_h_wire is true, used to filter by account.
     221             :    */
     222             :   struct TALER_MerchantWireHashP h_wire;
     223             : 
     224             :   /**
     225             :    * How long are we willing to wait for the exchange(s)?
     226             :    */
     227             :   struct GNUNET_TIME_Absolute timeout;
     228             : 
     229             :   /**
     230             :    * HTTP status code to use for the reply, i.e 200 for "OK".
     231             :    * Special value UINT_MAX is used to indicate hard errors
     232             :    * (no reply, return #MHD_NO).
     233             :    */
     234             :   unsigned int response_code;
     235             : 
     236             :   /**
     237             :    * #GNUNET_NO if the @e connection was not suspended,
     238             :    * #GNUNET_YES if the @e connection was suspended,
     239             :    * #GNUNET_SYSERR if @e connection was resumed to as
     240             :    * part of #MH_force_pc_resume during shutdown.
     241             :    */
     242             :   enum GNUNET_GenericReturnValue suspended;
     243             : 
     244             :   /**
     245             :    * What state are we long-polling for?
     246             :    */
     247             :   enum TALER_EXCHANGE_KycLongPollTarget lpt;
     248             : 
     249             :   /**
     250             :    * True if @e h_wire was given.
     251             :    */
     252             :   bool have_h_wire;
     253             : 
     254             :   /**
     255             :    * We're still waiting on the exchange to determine
     256             :    * the KYC status of our deposit(s).
     257             :    */
     258             :   bool return_immediately;
     259             : 
     260             : };
     261             : 
     262             : 
     263             : /**
     264             :  * Head of DLL.
     265             :  */
     266             : static struct KycContext *kc_head;
     267             : 
     268             : /**
     269             :  * Tail of DLL.
     270             :  */
     271             : static struct KycContext *kc_tail;
     272             : 
     273             : 
     274             : void
     275          14 : TMH_force_kyc_resume ()
     276             : {
     277          14 :   for (struct KycContext *kc = kc_head;
     278          14 :        NULL != kc;
     279           0 :        kc = kc->next)
     280             :   {
     281           0 :     if (GNUNET_YES == kc->suspended)
     282             :     {
     283           0 :       kc->suspended = GNUNET_SYSERR;
     284           0 :       MHD_resume_connection (kc->connection);
     285             :     }
     286             :   }
     287          14 : }
     288             : 
     289             : 
     290             : /**
     291             :  * Custom cleanup routine for a `struct KycContext`.
     292             :  *
     293             :  * @param cls the `struct KycContext` to clean up.
     294             :  */
     295             : static void
     296           9 : kyc_context_cleanup (void *cls)
     297             : {
     298           9 :   struct KycContext *kc = cls;
     299             :   struct ExchangeKycRequest *ekr;
     300             : 
     301           9 :   while (NULL != (ekr = kc->exchange_pending_head))
     302             :   {
     303           0 :     GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
     304             :                                  kc->exchange_pending_tail,
     305             :                                  ekr);
     306           0 :     if (NULL != ekr->fo)
     307             :     {
     308           0 :       TMH_EXCHANGES_keys4exchange_cancel (ekr->fo);
     309           0 :       ekr->fo = NULL;
     310             :     }
     311           0 :     json_decref (ekr->pkaa);
     312           0 :     json_decref (ekr->jlimits);
     313           0 :     if (NULL != ekr->keys)
     314           0 :       TALER_EXCHANGE_keys_decref (ekr->keys);
     315           0 :     GNUNET_free (ekr->exchange_url);
     316           0 :     GNUNET_free (ekr->payto_uri.full_payto);
     317           0 :     GNUNET_free (ekr);
     318             :   }
     319           9 :   if (NULL != kc->eh)
     320             :   {
     321           4 :     TMH_db->event_listen_cancel (kc->eh);
     322           4 :     kc->eh = NULL;
     323             :   }
     324           9 :   if (NULL != kc->response)
     325             :   {
     326           7 :     MHD_destroy_response (kc->response);
     327           7 :     kc->response = NULL;
     328             :   }
     329           9 :   GNUNET_CONTAINER_DLL_remove (kc_head,
     330             :                                kc_tail,
     331             :                                kc);
     332           9 :   json_decref (kc->kycs_data);
     333           9 :   GNUNET_free (kc);
     334           9 : }
     335             : 
     336             : 
     337             : /**
     338             :  * Resume the given KYC context and send the final response.  Stores the
     339             :  * response in the @a kc and signals MHD to resume the connection.  Also
     340             :  * ensures MHD runs immediately.
     341             :  *
     342             :  * @param kc KYC context
     343             :  */
     344             : static void
     345           7 : resume_kyc_with_response (struct KycContext *kc)
     346             : {
     347           7 :   kc->response_code = MHD_HTTP_OK;
     348           7 :   kc->response = TALER_MHD_MAKE_JSON_PACK (
     349             :     GNUNET_JSON_pack_array_incref ("kyc_data",
     350             :                                    kc->kycs_data));
     351           7 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     352             :               "Resuming /kyc handling as exchange interaction is done (%u)\n",
     353             :               MHD_HTTP_OK);
     354           7 :   if (GNUNET_YES == kc->suspended)
     355             :   {
     356           3 :     kc->suspended = GNUNET_NO;
     357           3 :     MHD_resume_connection (kc->connection);
     358           3 :     TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
     359             :   }
     360           7 : }
     361             : 
     362             : 
     363             : /**
     364             :  * Handle a DB event about an update relevant
     365             :  * for the processing of the kyc request.
     366             :  *
     367             :  * @param cls our `struct KycContext`
     368             :  * @param extra additional event data provided
     369             :  * @param extra_size number of bytes in @a extra
     370             :  */
     371             : static void
     372           1 : kyc_change_cb (void *cls,
     373             :                const void *extra,
     374             :                size_t extra_size)
     375             : {
     376           1 :   struct KycContext *kc = cls;
     377             : 
     378           1 :   GNUNET_assert (GNUNET_YES == kc->suspended);
     379           1 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     380             :               "Resuming KYC with gateway timeout\n");
     381           1 :   kc->suspended = GNUNET_NO;
     382           1 :   MHD_resume_connection (kc->connection);
     383           1 :   TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
     384           1 : }
     385             : 
     386             : 
     387             : /**
     388             :  * Pack the given @a limit into the JSON @a limits array.
     389             :  *
     390             :  * @param limit account limit to pack
     391             :  * @param[in,out] limits JSON array to extend
     392             :  */
     393             : static void
     394           0 : pack_limit (const struct TALER_EXCHANGE_AccountLimit *limit,
     395             :             json_t *limits)
     396             : {
     397             :   json_t *jl;
     398             : 
     399           0 :   jl = GNUNET_JSON_PACK (
     400             :     TALER_JSON_pack_kycte ("operation_type",
     401             :                            limit->operation_type),
     402             :     GNUNET_JSON_pack_time_rel ("timeframe",
     403             :                                limit->timeframe),
     404             :     TALER_JSON_pack_amount ("threshold",
     405             :                             &limit->threshold),
     406             :     GNUNET_JSON_pack_bool ("soft_limit",
     407             :                            limit->soft_limit)
     408             :     );
     409           0 :   GNUNET_assert (0 ==
     410             :                  json_array_append_new (limits,
     411             :                                         jl));
     412           0 : }
     413             : 
     414             : 
     415             : /**
     416             :  * Return JSON array with AccountLimit objects giving
     417             :  * the current limits for this exchange.
     418             :  *
     419             :  * @param[in,out] ekr overall request context
     420             :  */
     421             : static json_t *
     422           9 : get_exchange_limits (
     423             :   struct ExchangeKycRequest *ekr)
     424             : {
     425           9 :   const struct TALER_EXCHANGE_Keys *keys = ekr->keys;
     426             :   json_t *limits;
     427             : 
     428           9 :   if (NULL != ekr->jlimits)
     429             :   {
     430           4 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     431             :                 "Returning custom KYC limits\n");
     432           4 :     return json_incref (ekr->jlimits);
     433             :   }
     434           5 :   if (NULL == keys)
     435             :   {
     436           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     437             :                 "No keys, thus no default KYC limits known\n");
     438           0 :     return NULL;
     439             :   }
     440           5 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     441             :               "Returning default KYC limits (%u/%u)\n",
     442             :               keys->hard_limits_length,
     443             :               keys->zero_limits_length);
     444           5 :   limits = json_array ();
     445           5 :   GNUNET_assert (NULL != limits);
     446           5 :   for (unsigned int i = 0; i<keys->hard_limits_length; i++)
     447             :   {
     448           0 :     const struct TALER_EXCHANGE_AccountLimit *limit
     449           0 :       = &keys->hard_limits[i];
     450             : 
     451           0 :     pack_limit (limit,
     452             :                 limits);
     453             :   }
     454           7 :   for (unsigned int i = 0; i<keys->zero_limits_length; i++)
     455             :   {
     456           2 :     const struct TALER_EXCHANGE_ZeroLimitedOperation *zlimit
     457           2 :       = &keys->zero_limits[i];
     458             :     json_t *jl;
     459             :     struct TALER_Amount zero;
     460             : 
     461           2 :     GNUNET_assert (GNUNET_OK ==
     462             :                    TALER_amount_set_zero (keys->currency,
     463             :                                           &zero));
     464           2 :     jl = GNUNET_JSON_PACK (
     465             :       TALER_JSON_pack_kycte ("operation_type",
     466             :                              zlimit->operation_type),
     467             :       GNUNET_JSON_pack_time_rel ("timeframe",
     468             :                                  GNUNET_TIME_UNIT_ZERO),
     469             :       TALER_JSON_pack_amount ("threshold",
     470             :                               &zero),
     471             :       GNUNET_JSON_pack_bool ("soft_limit",
     472             :                              true)
     473             :       );
     474           2 :     GNUNET_assert (0 ==
     475             :                    json_array_append_new (limits,
     476             :                                           jl));
     477             :   }
     478           5 :   return limits;
     479             : }
     480             : 
     481             : 
     482             : /**
     483             :  * Maps @a ekr to a status code for clients to interpret the
     484             :  * overall result.
     485             :  *
     486             :  * @param ekr request summary
     487             :  * @return status of the KYC state as a string
     488             :  */
     489             : static const char *
     490           9 : map_to_status (const struct ExchangeKycRequest *ekr)
     491             : {
     492           9 :   if (ekr->no_keys)
     493             :   {
     494           0 :     return "no-exchange-keys";
     495             :   }
     496           9 :   if (ekr->kyc_ok)
     497             :   {
     498           7 :     if (NULL != ekr->jlimits)
     499             :     {
     500             :       size_t off;
     501             :       json_t *limit;
     502           4 :       json_array_foreach (ekr->jlimits, off, limit)
     503             :       {
     504             :         struct TALER_Amount threshold;
     505             :         enum TALER_KYCLOGIC_KycTriggerEvent operation_type;
     506           4 :         bool soft = false;
     507             :         struct GNUNET_JSON_Specification spec[] = {
     508           4 :           TALER_JSON_spec_kycte ("operation_type",
     509             :                                  &operation_type),
     510           4 :           TALER_JSON_spec_amount_any ("threshold",
     511             :                                       &threshold),
     512           4 :           GNUNET_JSON_spec_mark_optional (
     513             :             GNUNET_JSON_spec_bool ("soft_limit",
     514             :                                    &soft),
     515             :             NULL),
     516           4 :           GNUNET_JSON_spec_end ()
     517             :         };
     518             : 
     519           4 :         if (GNUNET_OK !=
     520           4 :             GNUNET_JSON_parse (limit,
     521             :                                spec,
     522             :                                NULL, NULL))
     523             :         {
     524           0 :           GNUNET_break (0);
     525           4 :           return "merchant-internal-error";
     526             :         }
     527           4 :         if (! TALER_amount_is_zero (&threshold))
     528           0 :           continue; /* only care about zero-limits */
     529           4 :         if (! soft)
     530           0 :           continue; /* only care about soft limits */
     531           4 :         if ( (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT) ||
     532           4 :              (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_AGGREGATE) ||
     533           0 :              (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION) )
     534             :         {
     535           4 :           if (! ekr->auth_ok)
     536             :           {
     537           0 :             if (ekr->kyc_auth_conflict)
     538           0 :               return "kyc-wire-impossible";
     539           0 :             return "kyc-wire-required";
     540             :           }
     541           4 :           return "kyc-required";
     542             :         }
     543             :       }
     544             :     }
     545           3 :     if (NULL == ekr->jlimits)
     546             :     {
     547             :       /* check default limits */
     548           3 :       const struct TALER_EXCHANGE_Keys *keys = ekr->keys;
     549             : 
     550           3 :       for (unsigned int i = 0; i < keys->zero_limits_length; i++)
     551             :       {
     552           0 :         enum TALER_KYCLOGIC_KycTriggerEvent operation_type
     553           0 :           = keys->zero_limits[i].operation_type;
     554             : 
     555           0 :         if ( (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT) ||
     556           0 :              (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_AGGREGATE) ||
     557             :              (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION) )
     558             :         {
     559           0 :           if (! ekr->auth_ok)
     560             :           {
     561           0 :             if (ekr->kyc_auth_conflict)
     562           0 :               return "kyc-wire-impossible";
     563           0 :             return "kyc-wire-required";
     564             :           }
     565           0 :           return "kyc-required";
     566             :         }
     567             :       }
     568             :     }
     569           3 :     return "ready";
     570             :   }
     571           2 :   if (! ekr->auth_ok)
     572             :   {
     573           2 :     if (ekr->kyc_auth_conflict)
     574           0 :       return "kyc-wire-impossible";
     575           2 :     return "kyc-wire-required";
     576             :   }
     577           0 :   if (ekr->in_aml_review)
     578           0 :     return "awaiting-aml-review";
     579           0 :   switch (ekr->last_http_status)
     580             :   {
     581           0 :   case 0:
     582           0 :     return "exchange-unreachable";
     583           0 :   case MHD_HTTP_OK:
     584             :     /* then we should have kyc_ok */
     585           0 :     GNUNET_break (0);
     586           0 :     return NULL;
     587           0 :   case MHD_HTTP_ACCEPTED:
     588             :     /* Then KYC is really what  is needed */
     589           0 :     return "kyc-required";
     590           0 :   case MHD_HTTP_NO_CONTENT:
     591             :     /* then we should have had kyc_ok! */
     592           0 :     GNUNET_break (0);
     593           0 :     return NULL;
     594           0 :   case MHD_HTTP_FORBIDDEN:
     595             :     /* then we should have had ! auth_ok */
     596           0 :     GNUNET_break (0);
     597           0 :     return NULL;
     598           0 :   case MHD_HTTP_NOT_FOUND:
     599             :     /* then we should have had ! auth_ok */
     600           0 :     GNUNET_break (0);
     601           0 :     return NULL;
     602           0 :   case MHD_HTTP_CONFLICT:
     603             :     /* then we should have had ! auth_ok */
     604           0 :     GNUNET_break (0);
     605           0 :     return NULL;
     606           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     607           0 :     return "exchange-internal-error";
     608           0 :   case MHD_HTTP_GATEWAY_TIMEOUT:
     609           0 :     return "exchange-gateway-timeout";
     610           0 :   default:
     611           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     612             :                 "Exchange responded with unexpected HTTP status %u to /kyc-check request!\n",
     613             :                 ekr->last_http_status);
     614           0 :     break;
     615             :   }
     616           0 :   return "exchange-status-invalid";
     617             : }
     618             : 
     619             : 
     620             : /**
     621             :  * Take data from @a ekr to expand our response.
     622             :  *
     623             :  * @param ekr exchange we are done inspecting
     624             :  */
     625             : static void
     626           9 : ekr_expand_response (struct ExchangeKycRequest *ekr)
     627             : {
     628             :   const char *status;
     629             : 
     630           9 :   status = map_to_status (ekr);
     631           9 :   if (NULL == status)
     632             :   {
     633           0 :     GNUNET_break (0);
     634           0 :     status = "logic-bug";
     635             :   }
     636           9 :   GNUNET_assert (
     637             :     0 ==
     638             :     json_array_append_new (
     639             :       ekr->kc->kycs_data,
     640             :       GNUNET_JSON_PACK (
     641             :         TALER_JSON_pack_full_payto (
     642             :           "payto_uri",
     643             :           ekr->payto_uri),
     644             :         GNUNET_JSON_pack_data_auto (
     645             :           "h_wire",
     646             :           &ekr->h_wire),
     647             :         GNUNET_JSON_pack_string (
     648             :           "status",
     649             :           status),
     650             :         GNUNET_JSON_pack_string (
     651             :           "exchange_url",
     652             :           ekr->exchange_url),
     653             :         GNUNET_JSON_pack_bool ("no_keys",
     654             :                                ekr->no_keys),
     655             :         GNUNET_JSON_pack_bool ("auth_conflict",
     656             :                                ekr->kyc_auth_conflict),
     657             :         GNUNET_JSON_pack_uint64 ("exchange_http_status",
     658             :                                  ekr->last_http_status),
     659             :         (TALER_EC_NONE == ekr->last_ec)
     660             :         ? GNUNET_JSON_pack_allow_null (
     661             :           GNUNET_JSON_pack_string (
     662             :             "dummy",
     663             :             NULL))
     664             :         : GNUNET_JSON_pack_uint64 ("exchange_code",
     665             :                                    ekr->last_ec),
     666             :         ekr->auth_ok
     667             :         ? GNUNET_JSON_pack_data_auto (
     668             :           "access_token",
     669             :           &ekr->access_token)
     670             :         : GNUNET_JSON_pack_allow_null (
     671             :           GNUNET_JSON_pack_string (
     672             :             "dummy",
     673             :             NULL)),
     674             :         GNUNET_JSON_pack_allow_null (
     675             :           GNUNET_JSON_pack_array_steal (
     676             :             "limits",
     677             :             get_exchange_limits (ekr))),
     678             :         GNUNET_JSON_pack_allow_null (
     679             :           GNUNET_JSON_pack_array_incref ("payto_kycauths",
     680             :                                          ekr->pkaa))
     681             :         )));
     682           9 : }
     683             : 
     684             : 
     685             : /**
     686             :  * We are done with the KYC request @a ekr.  Remove it from the work list and
     687             :  * check if we are done overall.
     688             :  *
     689             :  * @param[in] ekr key request that is done (and will be freed)
     690             :  */
     691             : static void
     692           9 : ekr_finished (struct ExchangeKycRequest *ekr)
     693             : {
     694           9 :   struct KycContext *kc = ekr->kc;
     695             : 
     696           9 :   ekr_expand_response (ekr);
     697           9 :   GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head,
     698             :                                kc->exchange_pending_tail,
     699             :                                ekr);
     700           9 :   json_decref (ekr->jlimits);
     701           9 :   json_decref (ekr->pkaa);
     702           9 :   if (NULL != ekr->keys)
     703           5 :     TALER_EXCHANGE_keys_decref (ekr->keys);
     704           9 :   GNUNET_free (ekr->exchange_url);
     705           9 :   GNUNET_free (ekr->payto_uri.full_payto);
     706           9 :   GNUNET_free (ekr);
     707             : 
     708           9 :   if (NULL != kc->exchange_pending_head)
     709           1 :     return; /* wait for more */
     710             : 
     711           8 :   if ( (! kc->return_immediately) &&
     712           1 :        (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
     713             :   {
     714           1 :     if (GNUNET_NO == kc->suspended)
     715             :     {
     716           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     717             :                   "Suspending: long poll target %d not reached\n",
     718             :                   kc->lpt);
     719           0 :       MHD_suspend_connection (kc->connection);
     720           0 :       kc->suspended = GNUNET_YES;
     721             :     }
     722             :     else
     723             :     {
     724           1 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     725             :                   "Remaining suspended: long poll target %d not reached\n",
     726             :                   kc->lpt);
     727             :     }
     728           1 :     return;
     729             :   }
     730             :   /* All exchange requests done, create final
     731             :      big response from cumulated replies */
     732           7 :   resume_kyc_with_response (kc);
     733             : }
     734             : 
     735             : 
     736             : /**
     737             :  * Figure out which exchange accounts from @a keys could
     738             :  * be used for a KYC auth wire transfer from the account
     739             :  * that @a ekr is checking. Will set the "pkaa" array
     740             :  * in @a ekr.
     741             :  *
     742             :  * @param[in,out] ekr request we are processing
     743             :  */
     744             : static void
     745           5 : determine_eligible_accounts (
     746             :   struct ExchangeKycRequest *ekr)
     747             : {
     748           5 :   struct KycContext *kc = ekr->kc;
     749           5 :   const struct TALER_EXCHANGE_Keys *keys = ekr->keys;
     750             :   struct TALER_Amount kyc_amount;
     751             :   char *merchant_pub_str;
     752             :   struct TALER_NormalizedPayto np;
     753             : 
     754           5 :   ekr->pkaa = json_array ();
     755           5 :   GNUNET_assert (NULL != ekr->pkaa);
     756             :   {
     757             :     const struct TALER_EXCHANGE_GlobalFee *gf;
     758             : 
     759           5 :     gf = TALER_EXCHANGE_get_global_fee (keys,
     760             :                                         GNUNET_TIME_timestamp_get ());
     761           5 :     if (NULL == gf)
     762             :     {
     763           0 :       GNUNET_assert (GNUNET_OK ==
     764             :                      TALER_amount_set_zero (keys->currency,
     765             :                                             &kyc_amount));
     766             :     }
     767             :     else
     768             :     {
     769             :       /* FIXME-#9427: history fee should be globally renamed to KYC fee... */
     770           5 :       kyc_amount = gf->fees.history;
     771             :     }
     772             :   }
     773             : 
     774             :   merchant_pub_str
     775           5 :     = GNUNET_STRINGS_data_to_string_alloc (
     776           5 :         &kc->mi->merchant_pub,
     777             :         sizeof (kc->mi->merchant_pub));
     778             :   /* For all accounts of the exchange */
     779           5 :   np = TALER_payto_normalize (ekr->payto_uri);
     780          10 :   for (unsigned int i = 0; i<keys->accounts_len; i++)
     781             :   {
     782           5 :     const struct TALER_EXCHANGE_WireAccount *account
     783           5 :       = &keys->accounts[i];
     784             : 
     785             :     /* KYC auth transfers are never supported with conversion */
     786           5 :     if (NULL != account->conversion_url)
     787           0 :       continue;
     788             :     /* filter by source account by credit_restrictions */
     789           5 :     if (GNUNET_YES !=
     790           5 :         TALER_EXCHANGE_test_account_allowed (account,
     791             :                                              true, /* credit */
     792             :                                              np))
     793           0 :       continue;
     794             :     /* exchange account is allowed, add it */
     795             :     {
     796           5 :       const char *exchange_account_payto
     797             :         = account->fpayto_uri.full_payto;
     798             :       char *payto_kycauth;
     799             : 
     800           5 :       if (TALER_amount_is_zero (&kyc_amount))
     801           0 :         GNUNET_asprintf (&payto_kycauth,
     802             :                          "%s%cmessage=KYC:%s",
     803             :                          exchange_account_payto,
     804           0 :                          (NULL == strchr (exchange_account_payto,
     805             :                                           '?'))
     806             :                          ? '?'
     807             :                          : '&',
     808             :                          merchant_pub_str);
     809             :       else
     810          10 :         GNUNET_asprintf (&payto_kycauth,
     811             :                          "%s%camount=%s&message=KYC:%s",
     812             :                          exchange_account_payto,
     813           5 :                          (NULL == strchr (exchange_account_payto,
     814             :                                           '?'))
     815             :                          ? '?'
     816             :                          : '&',
     817             :                          TALER_amount2s (&kyc_amount),
     818             :                          merchant_pub_str);
     819           5 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     820             :                   "Found account %s where KYC auth is possible\n",
     821             :                   payto_kycauth);
     822           5 :       GNUNET_assert (0 ==
     823             :                      json_array_append_new (ekr->pkaa,
     824             :                                             json_string (payto_kycauth)));
     825           5 :       GNUNET_free (payto_kycauth);
     826             :     }
     827             :   }
     828           5 :   GNUNET_free (np.normalized_payto);
     829           5 :   GNUNET_free (merchant_pub_str);
     830           5 : }
     831             : 
     832             : 
     833             : /**
     834             :  * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
     835             :  * operation.  Runs the KYC check against the exchange.
     836             :  *
     837             :  * @param cls closure with our `struct ExchangeKycRequest *`
     838             :  * @param keys keys of the exchange context
     839             :  * @param exchange representation of the exchange
     840             :  */
     841             : static void
     842           5 : kyc_with_exchange (void *cls,
     843             :                    struct TALER_EXCHANGE_Keys *keys,
     844             :                    struct TMH_Exchange *exchange)
     845             : {
     846           5 :   struct ExchangeKycRequest *ekr = cls;
     847             : 
     848             :   (void) exchange;
     849           5 :   ekr->fo = NULL;
     850           5 :   if (NULL == keys)
     851             :   {
     852           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     853             :                 "Failed to download `%skeys`\n",
     854             :                 ekr->exchange_url);
     855           0 :     ekr->no_keys = true;
     856           0 :     ekr_finished (ekr);
     857           0 :     return;
     858             :   }
     859           5 :   ekr->keys = TALER_EXCHANGE_keys_incref (keys);
     860           5 :   if (! ekr->auth_ok)
     861             :   {
     862           5 :     determine_eligible_accounts (ekr);
     863           5 :     if (0 == json_array_size (ekr->pkaa))
     864             :     {
     865             :       /* No KYC auth wire transfers are possible to this exchange from
     866             :          our merchant bank account, so we cannot use this account with
     867             :          this exchange if it has any KYC requirements! */
     868           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     869             :                   "KYC auth to `%s' impossible for merchant account `%s'\n",
     870             :                   ekr->exchange_url,
     871             :                   ekr->payto_uri.full_payto);
     872           0 :       ekr->kyc_auth_conflict = true;
     873             :     }
     874             :   }
     875           5 :   ekr_finished (ekr);
     876             : }
     877             : 
     878             : 
     879             : /**
     880             :  * Function called from account_kyc_get_status() with KYC status information
     881             :  * for this merchant.
     882             :  *
     883             :  * @param cls our `struct KycContext *`
     884             :  * @param h_wire hash of the wire account
     885             :  * @param payto_uri payto:// URI of the merchant's bank account
     886             :  * @param exchange_url base URL of the exchange for which this is a status
     887             :  * @param last_check when did we last get an update on our KYC status from the exchange
     888             :  * @param kyc_ok true if we satisfied the KYC requirements
     889             :  * @param access_token access token for the KYC SPA, NULL if we cannot access it yet (need KYC auth wire transfer)
     890             :  * @param last_http_status last HTTP status from /kyc-check
     891             :  * @param last_ec last Taler error code from /kyc-check
     892             :  * @param in_aml_review true if the account is pending review
     893             :  * @param jlimits JSON array of applicable AccountLimits, or NULL if unknown (like defaults apply)
     894             :  */
     895             : static void
     896           9 : kyc_status_cb (
     897             :   void *cls,
     898             :   const struct TALER_MerchantWireHashP *h_wire,
     899             :   struct TALER_FullPayto payto_uri,
     900             :   const char *exchange_url,
     901             :   struct GNUNET_TIME_Timestamp last_check,
     902             :   bool kyc_ok,
     903             :   const struct TALER_AccountAccessTokenP *access_token,
     904             :   unsigned int last_http_status,
     905             :   enum TALER_ErrorCode last_ec,
     906             :   bool in_aml_review,
     907             :   const json_t *jlimits)
     908             : {
     909           9 :   struct KycContext *kc = cls;
     910             :   struct ExchangeKycRequest *ekr;
     911             : 
     912           9 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     913             :               "KYC status for `%s' at `%s' is %u/%s/%s/%s\n",
     914             :               payto_uri.full_payto,
     915             :               exchange_url,
     916             :               last_http_status,
     917             :               kyc_ok ? "KYC OK" : "KYC NEEDED",
     918             :               in_aml_review ? "IN AML REVIEW" : "NO AML REVIEW",
     919             :               NULL == jlimits ? "DEFAULT LIMITS" : "CUSTOM LIMITS");
     920           9 :   switch (kc->lpt)
     921             :   {
     922           4 :   case TALER_EXCHANGE_KLPT_NONE:
     923           4 :     break;
     924           4 :   case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
     925           4 :     if (NULL != access_token)
     926           3 :       kc->return_immediately = true;
     927           4 :     break;
     928           0 :   case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
     929           0 :     if (! in_aml_review)
     930           0 :       kc->return_immediately = true;
     931           0 :     break;
     932           1 :   case TALER_EXCHANGE_KLPT_KYC_OK:
     933           1 :     if (kyc_ok)
     934           1 :       kc->return_immediately = true;
     935           1 :     break;
     936             :   }
     937           9 :   ekr = GNUNET_new (struct ExchangeKycRequest);
     938           9 :   GNUNET_CONTAINER_DLL_insert (kc->exchange_pending_head,
     939             :                                kc->exchange_pending_tail,
     940             :                                ekr);
     941           9 :   ekr->last_http_status = last_http_status;
     942           9 :   ekr->last_ec = last_ec;
     943           9 :   if (NULL != jlimits)
     944           4 :     ekr->jlimits = json_incref ((json_t *) jlimits);
     945           9 :   ekr->h_wire = *h_wire;
     946           9 :   ekr->exchange_url = GNUNET_strdup (exchange_url);
     947             :   ekr->payto_uri.full_payto
     948           9 :     = GNUNET_strdup (payto_uri.full_payto);
     949           9 :   ekr->last_check = last_check;
     950           9 :   ekr->kyc_ok = kyc_ok;
     951           9 :   ekr->kc = kc;
     952           9 :   ekr->in_aml_review = in_aml_review;
     953           9 :   ekr->auth_ok = (NULL != access_token);
     954           9 :   if ( (! ekr->auth_ok) ||
     955           4 :        (NULL == ekr->jlimits) )
     956             :   {
     957             :     /* Figure out wire transfer instructions */
     958           5 :     if (GNUNET_NO == kc->suspended)
     959             :     {
     960           4 :       MHD_suspend_connection (kc->connection);
     961           4 :       kc->suspended = GNUNET_YES;
     962             :     }
     963           5 :     ekr->fo = TMH_EXCHANGES_keys4exchange (
     964             :       exchange_url,
     965             :       false,
     966             :       &kyc_with_exchange,
     967             :       ekr);
     968           5 :     if (NULL == ekr->fo)
     969             :     {
     970           0 :       GNUNET_break (0);
     971           0 :       ekr_finished (ekr);
     972           0 :       return;
     973             :     }
     974           5 :     return;
     975             :   }
     976           4 :   ekr->access_token = *access_token;
     977           4 :   ekr_finished (ekr);
     978             : }
     979             : 
     980             : 
     981             : /**
     982             :  * Check the KYC status of an instance.
     983             :  *
     984             :  * @param mi instance to check KYC status of
     985             :  * @param connection the MHD connection to handle
     986             :  * @param[in,out] hc context with further information about the request
     987             :  * @return MHD result code
     988             :  */
     989             : static MHD_RESULT
     990          13 : get_instances_ID_kyc (
     991             :   struct TMH_MerchantInstance *mi,
     992             :   struct MHD_Connection *connection,
     993             :   struct TMH_HandlerContext *hc)
     994             : {
     995          13 :   struct KycContext *kc = hc->ctx;
     996             : 
     997          13 :   if (NULL == kc)
     998             :   {
     999           9 :     kc = GNUNET_new (struct KycContext);
    1000           9 :     kc->mi = mi;
    1001           9 :     hc->ctx = kc;
    1002           9 :     hc->cc = &kyc_context_cleanup;
    1003           9 :     GNUNET_CONTAINER_DLL_insert (kc_head,
    1004             :                                  kc_tail,
    1005             :                                  kc);
    1006           9 :     kc->connection = connection;
    1007           9 :     kc->hc = hc;
    1008           9 :     kc->kycs_data = json_array ();
    1009           9 :     GNUNET_assert (NULL != kc->kycs_data);
    1010           9 :     TALER_MHD_parse_request_timeout (connection,
    1011             :                                      &kc->timeout);
    1012             :     {
    1013           9 :       uint64_t num = 0;
    1014             :       int val;
    1015             : 
    1016           9 :       TALER_MHD_parse_request_number (connection,
    1017             :                                       "lpt",
    1018             :                                       &num);
    1019           9 :       val = (int) num;
    1020           9 :       if ( (val < 0) ||
    1021             :            (val > TALER_EXCHANGE_KLPT_MAX) )
    1022             :       {
    1023             :         /* Protocol violation, but we can be graceful and
    1024             :            just ignore the long polling! */
    1025           0 :         GNUNET_break_op (0);
    1026           0 :         val = TALER_EXCHANGE_KLPT_NONE;
    1027             :       }
    1028           9 :       kc->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
    1029             :     }
    1030             :     kc->return_immediately
    1031           9 :       = (TALER_EXCHANGE_KLPT_NONE == kc->lpt);
    1032             :     /* process 'exchange_url' argument */
    1033           9 :     kc->exchange_url = MHD_lookup_connection_value (
    1034             :       connection,
    1035             :       MHD_GET_ARGUMENT_KIND,
    1036             :       "exchange_url");
    1037           9 :     if ( (NULL != kc->exchange_url) &&
    1038           7 :          ( (! TALER_url_valid_charset (kc->exchange_url)) ||
    1039           7 :            (! TALER_is_web_url (kc->exchange_url)) ) )
    1040             :     {
    1041           0 :       GNUNET_break_op (0);
    1042           0 :       return TALER_MHD_reply_with_error (
    1043             :         connection,
    1044             :         MHD_HTTP_BAD_REQUEST,
    1045             :         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1046             :         "exchange_url must be a valid HTTP(s) URL");
    1047             :     }
    1048             : 
    1049           9 :     TALER_MHD_parse_request_arg_auto (connection,
    1050             :                                       "h_wire",
    1051             :                                       &kc->h_wire,
    1052             :                                       kc->have_h_wire);
    1053             : 
    1054           9 :     if ( (TALER_EXCHANGE_KLPT_NONE != kc->lpt) &&
    1055           4 :          (! GNUNET_TIME_absolute_is_past (kc->timeout)) )
    1056             :     {
    1057           4 :       if (kc->have_h_wire)
    1058             :       {
    1059           2 :         struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = {
    1060           2 :           .header.size = htons (sizeof (ev)),
    1061           2 :           .header.type = htons (
    1062             :             TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED
    1063             :             ),
    1064             :           .h_wire = kc->h_wire
    1065             :         };
    1066             : 
    1067           4 :         kc->eh = TMH_db->event_listen (
    1068           2 :           TMH_db->cls,
    1069             :           &ev.header,
    1070             :           GNUNET_TIME_absolute_get_remaining (kc->timeout),
    1071             :           &kyc_change_cb,
    1072             :           kc);
    1073             :       }
    1074             :       else
    1075             :       {
    1076           2 :         struct GNUNET_DB_EventHeaderP hdr = {
    1077           2 :           .size = htons (sizeof (hdr)),
    1078           2 :           .type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED)
    1079             :         };
    1080             : 
    1081           4 :         kc->eh = TMH_db->event_listen (
    1082           2 :           TMH_db->cls,
    1083             :           &hdr,
    1084             :           GNUNET_TIME_absolute_get_remaining (kc->timeout),
    1085             :           &kyc_change_cb,
    1086             :           kc);
    1087             :       }
    1088             :     } /* end register LISTEN hooks */
    1089             :   } /* end 1st time initialization */
    1090             : 
    1091          13 :   if (GNUNET_SYSERR == kc->suspended)
    1092           0 :     return MHD_NO; /* during shutdown, we don't generate any more replies */
    1093          13 :   GNUNET_assert (GNUNET_NO == kc->suspended);
    1094             : 
    1095          13 :   if (NULL != kc->response)
    1096           3 :     return MHD_queue_response (connection,
    1097             :                                kc->response_code,
    1098             :                                kc->response);
    1099             : 
    1100             :   /* Check our database */
    1101             :   {
    1102             :     enum GNUNET_DB_QueryStatus qs;
    1103             : 
    1104          10 :     GNUNET_break (0 ==
    1105             :                   json_array_clear (kc->kycs_data));
    1106          10 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1107             :                 "Checking KYC status for %s (%d/%s)\n",
    1108             :                 mi->settings.id,
    1109             :                 kc->have_h_wire,
    1110             :                 kc->exchange_url);
    1111          10 :     qs = TMH_db->account_kyc_get_status (
    1112          10 :       TMH_db->cls,
    1113          10 :       mi->settings.id,
    1114          10 :       kc->have_h_wire
    1115             :       ? &kc->h_wire
    1116             :       : NULL,
    1117             :       kc->exchange_url,
    1118             :       &kyc_status_cb,
    1119             :       kc);
    1120          10 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1121             :                 "account_kyc_get_status returned %d records\n",
    1122             :                 (int) qs);
    1123          10 :     if (qs < 0)
    1124             :     {
    1125             :       /* Database error */
    1126           0 :       GNUNET_break (0);
    1127           0 :       if (GNUNET_YES == kc->suspended)
    1128             :       {
    1129             :         /* must have suspended before DB error, resume! */
    1130           0 :         MHD_resume_connection (connection);
    1131           0 :         kc->suspended = GNUNET_NO;
    1132             :       }
    1133           0 :       return TALER_MHD_reply_with_ec (
    1134             :         connection,
    1135             :         TALER_EC_GENERIC_DB_FETCH_FAILED,
    1136             :         "account_kyc_get_status");
    1137             :     }
    1138          10 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    1139             :     {
    1140             :       /* no matching accounts, could not have suspended */
    1141           2 :       GNUNET_assert (GNUNET_NO == kc->suspended);
    1142           2 :       return TALER_MHD_reply_static (connection,
    1143             :                                      MHD_HTTP_NO_CONTENT,
    1144             :                                      NULL,
    1145             :                                      NULL,
    1146             :                                      0);
    1147             :     }
    1148             :   }
    1149           8 :   if (GNUNET_YES == kc->suspended)
    1150           4 :     return MHD_YES;
    1151             :   /* Should have generated a response */
    1152           4 :   GNUNET_break (NULL != kc->response);
    1153           4 :   return MHD_queue_response (connection,
    1154             :                              kc->response_code,
    1155             :                              kc->response);
    1156             : }
    1157             : 
    1158             : 
    1159             : MHD_RESULT
    1160          13 : TMH_private_get_instances_ID_kyc (
    1161             :   const struct TMH_RequestHandler *rh,
    1162             :   struct MHD_Connection *connection,
    1163             :   struct TMH_HandlerContext *hc)
    1164             : {
    1165          13 :   struct TMH_MerchantInstance *mi = hc->instance;
    1166             : 
    1167             :   (void) rh;
    1168          13 :   return get_instances_ID_kyc (mi,
    1169             :                                connection,
    1170             :                                hc);
    1171             : }
    1172             : 
    1173             : 
    1174             : MHD_RESULT
    1175           0 : TMH_private_get_instances_default_ID_kyc (
    1176             :   const struct TMH_RequestHandler *rh,
    1177             :   struct MHD_Connection *connection,
    1178             :   struct TMH_HandlerContext *hc)
    1179             : {
    1180             :   struct TMH_MerchantInstance *mi;
    1181             : 
    1182             :   (void) rh;
    1183           0 :   mi = TMH_lookup_instance (hc->infix);
    1184           0 :   if (NULL == mi)
    1185             :   {
    1186           0 :     return TALER_MHD_reply_with_error (
    1187             :       connection,
    1188             :       MHD_HTTP_NOT_FOUND,
    1189             :       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    1190           0 :       hc->infix);
    1191             :   }
    1192           0 :   return get_instances_ID_kyc (mi,
    1193             :                                connection,
    1194             :                                hc);
    1195             : }
    1196             : 
    1197             : 
    1198             : /* end of taler-merchant-httpd_private-get-instances-ID-kyc.c */

Generated by: LCOV version 1.16