LCOV - code coverage report
Current view: top level - lib - merchant_api_get_kyc.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 117 155 75.5 %
Date: 2025-06-23 16:22:09 Functions: 5 6 83.3 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2023--2024 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Lesser General Public License as published by the Free Software
       7             :   Foundation; either version 2.1, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Lesser General Public License along with
      14             :   TALER; see the file COPYING.LGPL.  If not, see
      15             :   <http://www.gnu.org/licenses/>
      16             : */
      17             : /**
      18             :  * @file merchant_api_get_kyc.c
      19             :  * @brief Implementation of the GET /kyc request of the merchant's HTTP API
      20             :  * @author Christian Grothoff
      21             :  */
      22             : #include "platform.h"
      23             : #include <curl/curl.h>
      24             : #include <jansson.h>
      25             : #include <microhttpd.h> /* just for HTTP status codes */
      26             : #include <gnunet/gnunet_util_lib.h>
      27             : #include <gnunet/gnunet_curl_lib.h>
      28             : #include "taler_merchant_service.h"
      29             : #include "merchant_api_curl_defaults.h"
      30             : #include <taler/taler_json_lib.h>
      31             : #include <taler/taler_signatures.h>
      32             : 
      33             : 
      34             : /**
      35             :  * Maximum length of the KYC arrays supported.
      36             :  */
      37             : #define MAX_KYC 1024
      38             : 
      39             : /**
      40             :  * Handle for a GET /kyc operation.
      41             :  */
      42             : struct TALER_MERCHANT_KycGetHandle
      43             : {
      44             :   /**
      45             :    * The url for this request.
      46             :    */
      47             :   char *url;
      48             : 
      49             :   /**
      50             :    * Handle for the request.
      51             :    */
      52             :   struct GNUNET_CURL_Job *job;
      53             : 
      54             :   /**
      55             :    * Function to call with the result.
      56             :    */
      57             :   TALER_MERCHANT_KycGetCallback cb;
      58             : 
      59             :   /**
      60             :    * Closure for @a cb.
      61             :    */
      62             :   void *cb_cls;
      63             : 
      64             :   /**
      65             :    * Reference to the execution context.
      66             :    */
      67             :   struct GNUNET_CURL_Context *ctx;
      68             : 
      69             : };
      70             : 
      71             : 
      72             : /**
      73             :  * Parse @a kyc response and call the continuation on success.
      74             :  *
      75             :  * @param kyc operation handle
      76             :  * @param[in,out] kr response details
      77             :  * @param jkyc array from the reply
      78             :  * @return #GNUNET_OK on success (callback was called)
      79             :  */
      80             : static enum GNUNET_GenericReturnValue
      81           5 : parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc,
      82             :            struct TALER_MERCHANT_KycResponse *kr,
      83             :            const json_t *jkyc)
      84             : {
      85           5 :   unsigned int num_kycs = (unsigned int) json_array_size (jkyc);
      86           5 :   unsigned int num_limits = 0;
      87           5 :   unsigned int num_kycauths = 0;
      88           5 :   unsigned int pos_limits = 0;
      89           5 :   unsigned int pos_kycauths = 0;
      90             : 
      91           5 :   if ( (json_array_size (jkyc) != (size_t) num_kycs) ||
      92             :        (num_kycs > MAX_KYC) )
      93             :   {
      94           0 :     GNUNET_break (0);
      95           0 :     return GNUNET_SYSERR;
      96             :   }
      97             : 
      98          10 :   for (unsigned int i = 0; i<num_kycs; i++)
      99             :   {
     100           5 :     const json_t *jlimits = NULL;
     101           5 :     const json_t *jkycauths = NULL;
     102             :     struct GNUNET_JSON_Specification spec[] = {
     103           5 :       GNUNET_JSON_spec_mark_optional (
     104             :         GNUNET_JSON_spec_array_const (
     105             :           "limits",
     106             :           &jlimits),
     107             :         NULL),
     108           5 :       GNUNET_JSON_spec_mark_optional (
     109             :         GNUNET_JSON_spec_array_const (
     110             :           "payto_kycauths",
     111             :           &jkycauths),
     112             :         NULL),
     113           5 :       GNUNET_JSON_spec_end ()
     114             :     };
     115             : 
     116           5 :     if (GNUNET_OK !=
     117           5 :         GNUNET_JSON_parse (json_array_get (jkyc,
     118             :                                            i),
     119             :                            spec,
     120             :                            NULL, NULL))
     121             :     {
     122           0 :       GNUNET_break (0);
     123           0 :       json_dumpf (json_array_get (jkyc,
     124             :                                   i),
     125             :                   stderr,
     126             :                   JSON_INDENT (2));
     127           0 :       return GNUNET_SYSERR;
     128             :     }
     129           5 :     num_limits += json_array_size (jlimits);
     130           5 :     num_kycauths += json_array_size (jkycauths);
     131             :   }
     132             : 
     133             : 
     134           5 :   {
     135           5 :     struct TALER_MERCHANT_AccountKycRedirectDetail kycs[
     136           5 :       GNUNET_NZL (num_kycs)];
     137           5 :     struct TALER_EXCHANGE_AccountLimit limits[
     138           5 :       GNUNET_NZL (num_limits)];
     139           5 :     struct TALER_FullPayto payto_kycauths[
     140           5 :       GNUNET_NZL (num_kycauths)];
     141             : 
     142           5 :     memset (kycs,
     143             :             0,
     144             :             sizeof (kycs));
     145          10 :     for (unsigned int i = 0; i<num_kycs; i++)
     146             :     {
     147           5 :       struct TALER_MERCHANT_AccountKycRedirectDetail *rd
     148             :         = &kycs[i];
     149           5 :       const json_t *jlimits = NULL;
     150           5 :       const json_t *jkycauths = NULL;
     151             :       uint32_t hs;
     152             :       struct GNUNET_JSON_Specification spec[] = {
     153           5 :         TALER_JSON_spec_full_payto_uri (
     154             :           "payto_uri",
     155             :           &rd->payto_uri),
     156           5 :         TALER_JSON_spec_web_url (
     157             :           "exchange_url",
     158             :           &rd->exchange_url),
     159           5 :         GNUNET_JSON_spec_uint32 (
     160             :           "exchange_http_status",
     161             :           &hs),
     162           5 :         GNUNET_JSON_spec_bool (
     163             :           "no_keys",
     164             :           &rd->no_keys),
     165           5 :         GNUNET_JSON_spec_bool (
     166             :           "auth_conflict",
     167             :           &rd->auth_conflict),
     168           5 :         GNUNET_JSON_spec_mark_optional (
     169             :           TALER_JSON_spec_ec (
     170             :             "exchange_code",
     171             :             &rd->exchange_code),
     172             :           NULL),
     173           5 :         GNUNET_JSON_spec_mark_optional (
     174           5 :           GNUNET_JSON_spec_fixed_auto (
     175             :             "access_token",
     176             :             &rd->access_token),
     177             :           &rd->no_access_token),
     178           5 :         GNUNET_JSON_spec_mark_optional (
     179             :           GNUNET_JSON_spec_array_const (
     180             :             "limits",
     181             :             &jlimits),
     182             :           NULL),
     183           5 :         GNUNET_JSON_spec_mark_optional (
     184             :           GNUNET_JSON_spec_array_const (
     185             :             "payto_kycauths",
     186             :             &jkycauths),
     187             :           NULL),
     188           5 :         GNUNET_JSON_spec_end ()
     189             :       };
     190             :       size_t j;
     191             :       json_t *jlimit;
     192             :       json_t *jkycauth;
     193             : 
     194           5 :       if (GNUNET_OK !=
     195           5 :           GNUNET_JSON_parse (json_array_get (jkyc,
     196             :                                              i),
     197             :                              spec,
     198             :                              NULL, NULL))
     199             :       {
     200           0 :         GNUNET_break (0);
     201           0 :         json_dumpf (json_array_get (jkyc,
     202             :                                     i),
     203             :                     stderr,
     204             :                     JSON_INDENT (2));
     205           0 :         return GNUNET_SYSERR;
     206             :       }
     207           5 :       rd->exchange_http_status = (unsigned int) hs;
     208           5 :       rd->limits = &limits[pos_limits];
     209           5 :       rd->limits_length = json_array_size (jlimits);
     210          10 :       json_array_foreach (jlimits, j, jlimit)
     211             :       {
     212           5 :         struct TALER_EXCHANGE_AccountLimit *limit
     213             :           = &limits[pos_limits];
     214             :         struct GNUNET_JSON_Specification jspec[] = {
     215           5 :           TALER_JSON_spec_kycte (
     216             :             "operation_type",
     217             :             &limit->operation_type),
     218           5 :           GNUNET_JSON_spec_relative_time (
     219             :             "timeframe",
     220             :             &limit->timeframe),
     221           5 :           TALER_JSON_spec_amount_any (
     222             :             "threshold",
     223             :             &limit->threshold),
     224           5 :           GNUNET_JSON_spec_mark_optional (
     225             :             GNUNET_JSON_spec_bool (
     226             :               "soft_limit",
     227             :               &limit->soft_limit),
     228             :             NULL),
     229           5 :           GNUNET_JSON_spec_end ()
     230             :         };
     231             : 
     232           5 :         GNUNET_assert (pos_limits < num_limits);
     233           5 :         limit->soft_limit = false;
     234           5 :         if (GNUNET_OK !=
     235           5 :             GNUNET_JSON_parse (jlimit,
     236             :                                jspec,
     237             :                                NULL, NULL))
     238             :         {
     239           0 :           GNUNET_break (0);
     240           0 :           json_dumpf (json_array_get (jkyc,
     241             :                                       i),
     242             :                       stderr,
     243             :                       JSON_INDENT (2));
     244           0 :           return GNUNET_SYSERR;
     245             :         }
     246           5 :         pos_limits++;
     247             :       }
     248           5 :       rd->payto_kycauths = &payto_kycauths[pos_kycauths];
     249           5 :       rd->pkycauth_length = json_array_size (jkycauths);
     250           6 :       json_array_foreach (jkycauths, j, jkycauth)
     251             :       {
     252           1 :         GNUNET_assert (pos_kycauths < num_kycauths);
     253             :         payto_kycauths[pos_kycauths].full_payto
     254           1 :           = (char *) json_string_value (jkycauth);
     255           1 :         if (NULL == payto_kycauths[pos_kycauths].full_payto)
     256             :         {
     257           0 :           GNUNET_break (0);
     258           0 :           json_dumpf (json_array_get (jkyc,
     259             :                                       i),
     260             :                       stderr,
     261             :                       JSON_INDENT (2));
     262           0 :           return GNUNET_SYSERR;
     263             :         }
     264           1 :         pos_kycauths++;
     265             :       }
     266             :     }
     267           5 :     kr->details.ok.kycs = kycs;
     268           5 :     kr->details.ok.kycs_length = num_kycs;
     269           5 :     kyc->cb (kyc->cb_cls,
     270             :              kr);
     271             :   }
     272           5 :   return GNUNET_OK;
     273             : }
     274             : 
     275             : 
     276             : /**
     277             :  * Function called when we're done processing the
     278             :  * HTTP /kyc request.
     279             :  *
     280             :  * @param cls the `struct TALER_MERCHANT_KycGetHandle`
     281             :  * @param response_code HTTP response code, 0 on error
     282             :  * @param response response body, NULL if not in JSON
     283             :  */
     284             : static void
     285           7 : handle_get_kyc_finished (void *cls,
     286             :                          long response_code,
     287             :                          const void *response)
     288             : {
     289           7 :   struct TALER_MERCHANT_KycGetHandle *kyc = cls;
     290           7 :   const json_t *json = response;
     291           7 :   struct TALER_MERCHANT_KycResponse kr = {
     292           7 :     .hr.http_status = (unsigned int) response_code,
     293             :     .hr.reply = json
     294             :   };
     295             : 
     296           7 :   kyc->job = NULL;
     297           7 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     298             :               "Got /kyc response with status code %u\n",
     299             :               (unsigned int) response_code);
     300           7 :   switch (response_code)
     301             :   {
     302           5 :   case MHD_HTTP_OK:
     303             :     {
     304             :       const json_t *jkyc;
     305             :       struct GNUNET_JSON_Specification spec[] = {
     306           5 :         GNUNET_JSON_spec_array_const ("kyc_data",
     307             :                                       &jkyc),
     308           5 :         GNUNET_JSON_spec_end ()
     309             :       };
     310             : 
     311           5 :       if (GNUNET_OK !=
     312           5 :           GNUNET_JSON_parse (json,
     313             :                              spec,
     314             :                              NULL, NULL))
     315             :       {
     316           0 :         kr.hr.http_status = 0;
     317           0 :         kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     318           0 :         break;
     319             :       }
     320           5 :       if (GNUNET_OK !=
     321           5 :           parse_kyc (kyc,
     322             :                      &kr,
     323             :                      jkyc))
     324             :       {
     325           0 :         kr.hr.http_status = 0;
     326           0 :         kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     327           0 :         break;
     328             :       }
     329             :       /* parse_kyc called the continuation already */
     330           5 :       TALER_MERCHANT_kyc_get_cancel (kyc);
     331           5 :       return;
     332             :     }
     333           2 :   case MHD_HTTP_NO_CONTENT:
     334           2 :     break;
     335           0 :   case MHD_HTTP_UNAUTHORIZED:
     336           0 :     kr.hr.ec = TALER_JSON_get_error_code (json);
     337           0 :     kr.hr.hint = TALER_JSON_get_error_hint (json);
     338             :     /* Nothing really to verify, merchant says we need to authenticate. */
     339           0 :     break;
     340           0 :   case MHD_HTTP_SERVICE_UNAVAILABLE:
     341           0 :     break;
     342           0 :   default:
     343             :     /* unexpected response code */
     344           0 :     kr.hr.ec = TALER_JSON_get_error_code (json);
     345           0 :     kr.hr.hint = TALER_JSON_get_error_hint (json);
     346           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     347             :                 "Unexpected response code %u/%d\n",
     348             :                 (unsigned int) response_code,
     349             :                 (int) kr.hr.ec);
     350           0 :     break;
     351             :   }
     352           2 :   kyc->cb (kyc->cb_cls,
     353             :            &kr);
     354           2 :   TALER_MERCHANT_kyc_get_cancel (kyc);
     355             : }
     356             : 
     357             : 
     358             : /**
     359             :  * Issue a GET KYC request to the backend.
     360             :  * Returns KYC status of bank accounts.
     361             :  *
     362             :  * @param ctx execution context
     363             :  * @param[in] url URL to use for the request, consumed!
     364             :  * @param h_wire which bank account to query, NULL for all
     365             :  * @param exchange_url which exchange to query, NULL for all
     366             :  * @param lpt target for long polling
     367             :  * @param timeout how long to wait for a reply
     368             :  * @param cb function to call with the result
     369             :  * @param cb_cls closure for @a cb
     370             :  * @return handle for this operation, NULL upon errors
     371             :  */
     372             : static struct TALER_MERCHANT_KycGetHandle *
     373           7 : kyc_get (struct GNUNET_CURL_Context *ctx,
     374             :          char *url,
     375             :          const struct TALER_MerchantWireHashP *h_wire,
     376             :          const char *exchange_url,
     377             :          enum TALER_EXCHANGE_KycLongPollTarget lpt,
     378             :          struct GNUNET_TIME_Relative timeout,
     379             :          TALER_MERCHANT_KycGetCallback cb,
     380             :          void *cb_cls)
     381             : {
     382             :   struct TALER_MERCHANT_KycGetHandle *kyc;
     383             :   CURL *eh;
     384             :   char timeout_ms[32];
     385             :   char lpt_str[32];
     386             :   unsigned long long tms;
     387             : 
     388           7 :   kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle);
     389           7 :   kyc->ctx = ctx;
     390           7 :   kyc->cb = cb;
     391           7 :   kyc->cb_cls = cb_cls;
     392           7 :   GNUNET_snprintf (lpt_str,
     393             :                    sizeof (lpt_str),
     394             :                    "%d",
     395             :                    (int) lpt);
     396          14 :   tms = timeout.rel_value_us
     397           7 :         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
     398           7 :   GNUNET_snprintf (timeout_ms,
     399             :                    sizeof (timeout_ms),
     400             :                    "%llu",
     401             :                    tms);
     402             :   kyc->url
     403          16 :     = TALER_url_join (
     404             :         url,
     405             :         "kyc",
     406             :         "h_wire",
     407             :         NULL == h_wire
     408             :         ? NULL
     409           2 :         : GNUNET_h2s_full (&h_wire->hash),
     410             :         "exchange_url",
     411             :         NULL == exchange_url
     412             :         ? NULL
     413             :         : exchange_url,
     414             :         "timeout_ms",
     415           7 :         GNUNET_TIME_relative_is_zero (timeout)
     416             :         ? NULL
     417             :         : timeout_ms,
     418             :         "lpt",
     419             :         TALER_EXCHANGE_KLPT_NONE == lpt
     420             :         ? NULL
     421             :         : lpt_str,
     422             :         NULL);
     423           7 :   GNUNET_free (url);
     424           7 :   if (NULL == kyc->url)
     425             :   {
     426           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     427             :                 "Could not construct request URL.\n");
     428           0 :     GNUNET_free (kyc);
     429           0 :     return NULL;
     430             :   }
     431           7 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     432             :               "Requesting URL '%s'\n",
     433             :               kyc->url);
     434           7 :   eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
     435           7 :   if (0 != tms)
     436             :   {
     437           4 :     GNUNET_break (CURLE_OK ==
     438             :                   curl_easy_setopt (eh,
     439             :                                     CURLOPT_TIMEOUT_MS,
     440             :                                     (long) (tms + 100L)));
     441             :   }
     442             :   kyc->job
     443           7 :     = GNUNET_CURL_job_add (ctx,
     444             :                            eh,
     445             :                            &handle_get_kyc_finished,
     446             :                            kyc);
     447           7 :   return kyc;
     448             : }
     449             : 
     450             : 
     451             : struct TALER_MERCHANT_KycGetHandle *
     452           7 : TALER_MERCHANT_kyc_get (
     453             :   struct GNUNET_CURL_Context *ctx,
     454             :   const char *backend_url,
     455             :   const struct TALER_MerchantWireHashP *h_wire,
     456             :   const char *exchange_url,
     457             :   enum TALER_EXCHANGE_KycLongPollTarget lpt,
     458             :   struct GNUNET_TIME_Relative timeout,
     459             :   TALER_MERCHANT_KycGetCallback cb,
     460             :   void *cb_cls)
     461             : {
     462             :   char *url;
     463             : 
     464           7 :   GNUNET_asprintf (&url,
     465             :                    "%sprivate/",
     466             :                    backend_url);
     467           7 :   return kyc_get (ctx,
     468             :                   url, /* consumed! */
     469             :                   h_wire,
     470             :                   exchange_url,
     471             :                   lpt,
     472             :                   timeout,
     473             :                   cb,
     474             :                   cb_cls);
     475             : }
     476             : 
     477             : 
     478             : struct TALER_MERCHANT_KycGetHandle *
     479           0 : TALER_MERCHANT_management_kyc_get (
     480             :   struct GNUNET_CURL_Context *ctx,
     481             :   const char *backend_url,
     482             :   const char *instance_id,
     483             :   const struct TALER_MerchantWireHashP *h_wire,
     484             :   const char *exchange_url,
     485             :   enum TALER_EXCHANGE_KycLongPollTarget lpt,
     486             :   struct GNUNET_TIME_Relative timeout,
     487             :   TALER_MERCHANT_KycGetCallback cb,
     488             :   void *cb_cls)
     489             : {
     490             :   char *url;
     491             : 
     492           0 :   GNUNET_asprintf (&url,
     493             :                    "%smanagement/instances/%s/",
     494             :                    backend_url,
     495             :                    instance_id);
     496           0 :   return kyc_get (ctx,
     497             :                   url, /* consumed! */
     498             :                   h_wire,
     499             :                   exchange_url,
     500             :                   lpt,
     501             :                   timeout,
     502             :                   cb,
     503             :                   cb_cls);
     504             : }
     505             : 
     506             : 
     507             : void
     508           7 : TALER_MERCHANT_kyc_get_cancel (
     509             :   struct TALER_MERCHANT_KycGetHandle *kyc)
     510             : {
     511           7 :   if (NULL != kyc->job)
     512           0 :     GNUNET_CURL_job_cancel (kyc->job);
     513           7 :   GNUNET_free (kyc->url);
     514           7 :   GNUNET_free (kyc);
     515           7 : }

Generated by: LCOV version 1.16