merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

merchant_api_get-private-kyc.c (17236B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023-2026 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-private-kyc-new.c
     19  * @brief Implementation of the GET /private/kyc request
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/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/taler-merchant/get-private-kyc.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include <taler/taler_json_lib.h>
     31 
     32 
     33 /**
     34  * Maximum length of the KYC arrays supported.
     35  */
     36 #define MAX_KYC 1024
     37 
     38 
     39 /**
     40  * Handle for a GET /private/kyc operation.
     41  */
     42 struct TALER_MERCHANT_GetPrivateKycHandle
     43 {
     44   /**
     45    * Base URL of the merchant backend.
     46    */
     47   char *base_url;
     48 
     49   /**
     50    * The full URL for this request.
     51    */
     52   char *url;
     53 
     54   /**
     55    * Handle for the request.
     56    */
     57   struct GNUNET_CURL_Job *job;
     58 
     59   /**
     60    * Function to call with the result.
     61    */
     62   TALER_MERCHANT_GetPrivateKycCallback cb;
     63 
     64   /**
     65    * Closure for @a cb.
     66    */
     67   TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls;
     68 
     69   /**
     70    * Reference to the execution context.
     71    */
     72   struct GNUNET_CURL_Context *ctx;
     73 
     74   /**
     75    * Hash of the wire account to filter by, or NULL.
     76    */
     77   const struct TALER_MerchantWireHashP *h_wire;
     78 
     79   /**
     80    * Storage for the h_wire value (if set).
     81    */
     82   struct TALER_MerchantWireHashP h_wire_val;
     83 
     84   /**
     85    * True if @e h_wire was set.
     86    */
     87   bool have_h_wire;
     88 
     89   /**
     90    * Exchange URL filter, or NULL.
     91    */
     92   char *exchange_url;
     93 
     94   /**
     95    * Long-poll target.
     96    */
     97   enum TALER_EXCHANGE_KycLongPollTarget lpt;
     98 
     99   /**
    100    * Long polling timeout.
    101    */
    102   struct GNUNET_TIME_Relative timeout;
    103 
    104   /**
    105    * Instance ID for management mode, or NULL.
    106    */
    107   char *instance_id;
    108 
    109   /**
    110    * Long-poll status filter, or NULL.
    111    */
    112   char *lp_status;
    113 
    114   /**
    115    * Long-poll negated status filter, or NULL.
    116    */
    117   char *lp_not_status;
    118 
    119   /**
    120    * Long-poll ETag to suppress unchanged responses.
    121    */
    122   struct GNUNET_ShortHashCode lp_not_etag;
    123 
    124   /**
    125    * True if @e lp_not_etag was set.
    126    */
    127   bool have_lp_not_etag;
    128 };
    129 
    130 
    131 /**
    132  * Parse @a jkyc response and call the continuation on success.
    133  *
    134  * @param kyc operation handle
    135  * @param[in,out] kr response details
    136  * @param jkyc array from the reply
    137  * @return #GNUNET_OK on success (callback was called)
    138  */
    139 static enum GNUNET_GenericReturnValue
    140 parse_kyc (struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
    141            struct TALER_MERCHANT_GetPrivateKycResponse *kr,
    142            const json_t *jkyc)
    143 {
    144   unsigned int num_kycs = (unsigned int) json_array_size (jkyc);
    145   unsigned int num_limits = 0;
    146   unsigned int num_kycauths = 0;
    147   unsigned int pos_limits = 0;
    148   unsigned int pos_kycauths = 0;
    149 
    150   if ( (json_array_size (jkyc) != (size_t) num_kycs) ||
    151        (num_kycs > MAX_KYC) )
    152   {
    153     GNUNET_break (0);
    154     return GNUNET_SYSERR;
    155   }
    156 
    157   for (unsigned int i = 0; i<num_kycs; i++)
    158   {
    159     const json_t *jlimits = NULL;
    160     const json_t *jkycauths = NULL;
    161     struct GNUNET_JSON_Specification spec[] = {
    162       GNUNET_JSON_spec_mark_optional (
    163         GNUNET_JSON_spec_array_const (
    164           "limits",
    165           &jlimits),
    166         NULL),
    167       GNUNET_JSON_spec_mark_optional (
    168         GNUNET_JSON_spec_array_const (
    169           "payto_kycauths",
    170           &jkycauths),
    171         NULL),
    172       GNUNET_JSON_spec_end ()
    173     };
    174 
    175     if (GNUNET_OK !=
    176         GNUNET_JSON_parse (json_array_get (jkyc,
    177                                            i),
    178                            spec,
    179                            NULL, NULL))
    180     {
    181       GNUNET_break (0);
    182       return GNUNET_SYSERR;
    183     }
    184     num_limits += json_array_size (jlimits);
    185     num_kycauths += json_array_size (jkycauths);
    186   }
    187 
    188   {
    189     struct TALER_MERCHANT_GetPrivateKycRedirectDetail kycs[
    190       GNUNET_NZL (num_kycs)];
    191     struct TALER_EXCHANGE_AccountLimit limits[
    192       GNUNET_NZL (num_limits)];
    193     struct TALER_FullPayto payto_kycauths[
    194       GNUNET_NZL (num_kycauths)];
    195 
    196     memset (kycs,
    197             0,
    198             sizeof (kycs));
    199     for (unsigned int i = 0; i<num_kycs; i++)
    200     {
    201       struct TALER_MERCHANT_GetPrivateKycRedirectDetail *rd
    202         = &kycs[i];
    203       const json_t *jlimits = NULL;
    204       const json_t *jkycauths = NULL;
    205       uint32_t hs;
    206       struct GNUNET_JSON_Specification spec[] = {
    207         TALER_JSON_spec_full_payto_uri (
    208           "payto_uri",
    209           &rd->payto_uri),
    210         TALER_JSON_spec_web_url (
    211           "exchange_url",
    212           &rd->exchange_url),
    213         GNUNET_JSON_spec_uint32 (
    214           "exchange_http_status",
    215           &hs),
    216         GNUNET_JSON_spec_bool (
    217           "no_keys",
    218           &rd->no_keys),
    219         GNUNET_JSON_spec_bool (
    220           "auth_conflict",
    221           &rd->auth_conflict),
    222         GNUNET_JSON_spec_mark_optional (
    223           TALER_JSON_spec_ec (
    224             "exchange_code",
    225             &rd->exchange_code),
    226           NULL),
    227         GNUNET_JSON_spec_mark_optional (
    228           GNUNET_JSON_spec_fixed_auto (
    229             "access_token",
    230             &rd->access_token),
    231           &rd->no_access_token),
    232         GNUNET_JSON_spec_fixed_auto (
    233           "h_wire",
    234           &rd->h_wire),
    235         GNUNET_JSON_spec_mark_optional (
    236           GNUNET_JSON_spec_string (
    237             "status",
    238             &rd->status),
    239           NULL),
    240         /* Mandatory since **v25** */
    241         GNUNET_JSON_spec_mark_optional (
    242           GNUNET_JSON_spec_string (
    243             "exchange_currency",
    244             &rd->exchange_currency),
    245           NULL),
    246         GNUNET_JSON_spec_mark_optional (
    247           GNUNET_JSON_spec_array_const (
    248             "limits",
    249             &jlimits),
    250           NULL),
    251         GNUNET_JSON_spec_mark_optional (
    252           GNUNET_JSON_spec_array_const (
    253             "payto_kycauths",
    254             &jkycauths),
    255           NULL),
    256         GNUNET_JSON_spec_end ()
    257       };
    258       size_t j;
    259       json_t *jlimit;
    260       json_t *jkycauth;
    261 
    262       if (GNUNET_OK !=
    263           GNUNET_JSON_parse (json_array_get (jkyc,
    264                                              i),
    265                              spec,
    266                              NULL, NULL))
    267       {
    268         GNUNET_break (0);
    269         return GNUNET_SYSERR;
    270       }
    271       rd->exchange_http_status = (unsigned int) hs;
    272       rd->limits = &limits[pos_limits];
    273       rd->limits_length = json_array_size (jlimits);
    274       json_array_foreach (jlimits, j, jlimit)
    275       {
    276         struct TALER_EXCHANGE_AccountLimit *limit
    277           = &limits[pos_limits];
    278         struct GNUNET_JSON_Specification jspec[] = {
    279           TALER_JSON_spec_kycte (
    280             "operation_type",
    281             &limit->operation_type),
    282           GNUNET_JSON_spec_relative_time (
    283             "timeframe",
    284             &limit->timeframe),
    285           TALER_JSON_spec_amount_any (
    286             "threshold",
    287             &limit->threshold),
    288           GNUNET_JSON_spec_mark_optional (
    289             GNUNET_JSON_spec_bool (
    290               "soft_limit",
    291               &limit->soft_limit),
    292             NULL),
    293           GNUNET_JSON_spec_end ()
    294         };
    295 
    296         GNUNET_assert (pos_limits < num_limits);
    297         limit->soft_limit = false;
    298         if (GNUNET_OK !=
    299             GNUNET_JSON_parse (jlimit,
    300                                jspec,
    301                                NULL, NULL))
    302         {
    303           GNUNET_break (0);
    304           return GNUNET_SYSERR;
    305         }
    306         pos_limits++;
    307       }
    308       rd->payto_kycauths = &payto_kycauths[pos_kycauths];
    309       rd->pkycauth_length = json_array_size (jkycauths);
    310       json_array_foreach (jkycauths, j, jkycauth)
    311       {
    312         GNUNET_assert (pos_kycauths < num_kycauths);
    313         payto_kycauths[pos_kycauths].full_payto
    314           = (char *) json_string_value (jkycauth);
    315         if (NULL == payto_kycauths[pos_kycauths].full_payto)
    316         {
    317           GNUNET_break (0);
    318           return GNUNET_SYSERR;
    319         }
    320         pos_kycauths++;
    321       }
    322     }
    323     kr->details.ok.kycs = kycs;
    324     kr->details.ok.kycs_length = num_kycs;
    325     kyc->cb (kyc->cb_cls,
    326              kr);
    327     kyc->cb = NULL;
    328   }
    329   return GNUNET_OK;
    330 }
    331 
    332 
    333 /**
    334  * Function called when we're done processing the
    335  * HTTP GET /private/kyc request.
    336  *
    337  * @param cls the `struct TALER_MERCHANT_GetPrivateKycHandle`
    338  * @param response_code HTTP response code, 0 on error
    339  * @param response response body, NULL if not in JSON
    340  */
    341 static void
    342 handle_get_kyc_finished (void *cls,
    343                          long response_code,
    344                          const void *response)
    345 {
    346   struct TALER_MERCHANT_GetPrivateKycHandle *kyc = cls;
    347   const json_t *json = response;
    348   struct TALER_MERCHANT_GetPrivateKycResponse kr = {
    349     .hr.http_status = (unsigned int) response_code,
    350     .hr.reply = json
    351   };
    352 
    353   kyc->job = NULL;
    354   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    355               "Got /private/kyc response with status code %u\n",
    356               (unsigned int) response_code);
    357   switch (response_code)
    358   {
    359   case MHD_HTTP_OK:
    360     {
    361       const json_t *jkyc;
    362       struct GNUNET_JSON_Specification spec[] = {
    363         GNUNET_JSON_spec_array_const ("kyc_data",
    364                                       &jkyc),
    365         GNUNET_JSON_spec_end ()
    366       };
    367 
    368       if (GNUNET_OK !=
    369           GNUNET_JSON_parse (json,
    370                              spec,
    371                              NULL, NULL))
    372       {
    373         kr.hr.http_status = 0;
    374         kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    375         break;
    376       }
    377       if (GNUNET_OK !=
    378           parse_kyc (kyc,
    379                      &kr,
    380                      jkyc))
    381       {
    382         kr.hr.http_status = 0;
    383         kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    384         break;
    385       }
    386       /* parse_kyc called the continuation already */
    387       TALER_MERCHANT_get_private_kyc_cancel (kyc);
    388       return;
    389     }
    390   case MHD_HTTP_NO_CONTENT:
    391     break;
    392   case MHD_HTTP_NOT_MODIFIED:
    393     /* ETag matched; nothing changed. No body expected. */
    394     break;
    395   case MHD_HTTP_BAD_REQUEST:
    396     kr.hr.ec = TALER_JSON_get_error_code (json);
    397     kr.hr.hint = TALER_JSON_get_error_hint (json);
    398     break;
    399   case MHD_HTTP_UNAUTHORIZED:
    400     kr.hr.ec = TALER_JSON_get_error_code (json);
    401     kr.hr.hint = TALER_JSON_get_error_hint (json);
    402     break;
    403   case MHD_HTTP_NOT_FOUND:
    404     kr.hr.ec = TALER_JSON_get_error_code (json);
    405     kr.hr.hint = TALER_JSON_get_error_hint (json);
    406     break;
    407   case MHD_HTTP_NOT_ACCEPTABLE:
    408     kr.hr.ec = TALER_JSON_get_error_code (json);
    409     kr.hr.hint = TALER_JSON_get_error_hint (json);
    410     break;
    411   case MHD_HTTP_SERVICE_UNAVAILABLE:
    412     break;
    413   default:
    414     kr.hr.ec = TALER_JSON_get_error_code (json);
    415     kr.hr.hint = TALER_JSON_get_error_hint (json);
    416     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    417                 "Unexpected response code %u/%d\n",
    418                 (unsigned int) response_code,
    419                 (int) kr.hr.ec);
    420     break;
    421   }
    422   kyc->cb (kyc->cb_cls,
    423            &kr);
    424   TALER_MERCHANT_get_private_kyc_cancel (kyc);
    425 }
    426 
    427 
    428 struct TALER_MERCHANT_GetPrivateKycHandle *
    429 TALER_MERCHANT_get_private_kyc_create (
    430   struct GNUNET_CURL_Context *ctx,
    431   const char *url)
    432 {
    433   struct TALER_MERCHANT_GetPrivateKycHandle *kyc;
    434 
    435   kyc = GNUNET_new (struct TALER_MERCHANT_GetPrivateKycHandle);
    436   kyc->ctx = ctx;
    437   kyc->base_url = GNUNET_strdup (url);
    438   kyc->lpt = TALER_EXCHANGE_KLPT_NONE;
    439   return kyc;
    440 }
    441 
    442 
    443 enum GNUNET_GenericReturnValue
    444 TALER_MERCHANT_get_private_kyc_set_options_ (
    445   struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
    446   unsigned int num_options,
    447   const struct TALER_MERCHANT_GetPrivateKycOptionValue *options)
    448 {
    449   for (unsigned int i = 0; i < num_options; i++)
    450   {
    451     const struct TALER_MERCHANT_GetPrivateKycOptionValue *opt =
    452       &options[i];
    453 
    454     switch (opt->option)
    455     {
    456     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_END:
    457       return GNUNET_OK;
    458     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_H_WIRE:
    459       if (NULL != opt->details.h_wire)
    460       {
    461         kyc->h_wire_val = *opt->details.h_wire;
    462         kyc->h_wire = &kyc->h_wire_val;
    463         kyc->have_h_wire = true;
    464       }
    465       break;
    466     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_EXCHANGE_URL:
    467       GNUNET_free (kyc->exchange_url);
    468       if (NULL != opt->details.exchange_url)
    469         kyc->exchange_url = GNUNET_strdup (opt->details.exchange_url);
    470       break;
    471     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LPT:
    472       kyc->lpt = opt->details.lpt;
    473       break;
    474     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_TIMEOUT:
    475       kyc->timeout = opt->details.timeout;
    476       break;
    477     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_INSTANCE_ID:
    478       GNUNET_free (kyc->instance_id);
    479       if (NULL != opt->details.instance_id)
    480         kyc->instance_id = GNUNET_strdup (opt->details.instance_id);
    481       break;
    482     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_STATUS:
    483       GNUNET_free (kyc->lp_status);
    484       if (NULL != opt->details.lp_status)
    485         kyc->lp_status = GNUNET_strdup (opt->details.lp_status);
    486       break;
    487     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_STATUS:
    488       GNUNET_free (kyc->lp_not_status);
    489       if (NULL != opt->details.lp_not_status)
    490         kyc->lp_not_status = GNUNET_strdup (opt->details.lp_not_status);
    491       break;
    492     case TALER_MERCHANT_GET_PRIVATE_KYC_OPTION_LP_NOT_ETAG:
    493       if (NULL != opt->details.lp_not_etag)
    494       {
    495         kyc->lp_not_etag = *opt->details.lp_not_etag;
    496         kyc->have_lp_not_etag = true;
    497       }
    498       break;
    499     default:
    500       GNUNET_break (0);
    501       return GNUNET_NO;
    502     }
    503   }
    504   return GNUNET_OK;
    505 }
    506 
    507 
    508 enum TALER_ErrorCode
    509 TALER_MERCHANT_get_private_kyc_start (
    510   struct TALER_MERCHANT_GetPrivateKycHandle *kyc,
    511   TALER_MERCHANT_GetPrivateKycCallback cb,
    512   TALER_MERCHANT_GET_PRIVATE_KYC_RESULT_CLOSURE *cb_cls)
    513 {
    514   CURL *eh;
    515   unsigned long long tms;
    516   char timeout_ms[32];
    517   char lpt_str[32];
    518   char *base_path;
    519 
    520   kyc->cb = cb;
    521   kyc->cb_cls = cb_cls;
    522 
    523   /* Build the base path depending on whether instance_id is set */
    524   if (NULL != kyc->instance_id)
    525   {
    526     GNUNET_asprintf (&base_path,
    527                      "%smanagement/instances/%s/",
    528                      kyc->base_url,
    529                      kyc->instance_id);
    530   }
    531   else
    532   {
    533     GNUNET_asprintf (&base_path,
    534                      "%sprivate/",
    535                      kyc->base_url);
    536   }
    537 
    538   GNUNET_snprintf (lpt_str,
    539                    sizeof (lpt_str),
    540                    "%d",
    541                    (int) kyc->lpt);
    542   tms = kyc->timeout.rel_value_us
    543         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    544   GNUNET_snprintf (timeout_ms,
    545                    sizeof (timeout_ms),
    546                    "%llu",
    547                    tms);
    548   {
    549     char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1];
    550 
    551     if (kyc->have_lp_not_etag)
    552     {
    553       char *end;
    554 
    555       end = GNUNET_STRINGS_data_to_string (
    556         &kyc->lp_not_etag,
    557         sizeof (kyc->lp_not_etag),
    558         etag_str,
    559         sizeof (etag_str) - 1);
    560       *end = '\0';
    561     }
    562     kyc->url
    563       = TALER_url_join (
    564           base_path,
    565           "kyc",
    566           "h_wire",
    567           kyc->have_h_wire
    568           ? GNUNET_h2s_full (&kyc->h_wire_val.hash)
    569           : NULL,
    570           "exchange_url",
    571           kyc->exchange_url,
    572           "timeout_ms",
    573           GNUNET_TIME_relative_is_zero (kyc->timeout)
    574           ? NULL
    575           : timeout_ms,
    576           "lpt",
    577           TALER_EXCHANGE_KLPT_NONE == kyc->lpt
    578           ? NULL
    579           : lpt_str,
    580           "lp_status",
    581           kyc->lp_status,
    582           "lp_not_status",
    583           kyc->lp_not_status,
    584           "lp_not_etag",
    585           kyc->have_lp_not_etag
    586           ? etag_str
    587           : NULL,
    588           NULL);
    589   }
    590   GNUNET_free (base_path);
    591   if (NULL == kyc->url)
    592     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    593   eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
    594   if (NULL == eh)
    595     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    596   if (0 != tms)
    597   {
    598     GNUNET_break (CURLE_OK ==
    599                   curl_easy_setopt (eh,
    600                                     CURLOPT_TIMEOUT_MS,
    601                                     (long) (tms + 100L)));
    602   }
    603   kyc->job = GNUNET_CURL_job_add (kyc->ctx,
    604                                   eh,
    605                                   &handle_get_kyc_finished,
    606                                   kyc);
    607   if (NULL == kyc->job)
    608     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    609   return TALER_EC_NONE;
    610 }
    611 
    612 
    613 void
    614 TALER_MERCHANT_get_private_kyc_cancel (
    615   struct TALER_MERCHANT_GetPrivateKycHandle *kyc)
    616 {
    617   if (NULL != kyc->job)
    618   {
    619     GNUNET_CURL_job_cancel (kyc->job);
    620     kyc->job = NULL;
    621   }
    622   GNUNET_free (kyc->url);
    623   GNUNET_free (kyc->exchange_url);
    624   GNUNET_free (kyc->instance_id);
    625   GNUNET_free (kyc->lp_status);
    626   GNUNET_free (kyc->lp_not_status);
    627   GNUNET_free (kyc->base_url);
    628   GNUNET_free (kyc);
    629 }
    630 
    631 
    632 /* end of merchant_api_get-private-kyc-new.c */