merchant

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

merchant_api_get_kyc.c (14342B)


      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 parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc,
     82            struct TALER_MERCHANT_KycResponse *kr,
     83            const json_t *jkyc)
     84 {
     85   unsigned int num_kycs = (unsigned int) json_array_size (jkyc);
     86   unsigned int num_limits = 0;
     87   unsigned int num_kycauths = 0;
     88   unsigned int pos_limits = 0;
     89   unsigned int pos_kycauths = 0;
     90 
     91   if ( (json_array_size (jkyc) != (size_t) num_kycs) ||
     92        (num_kycs > MAX_KYC) )
     93   {
     94     GNUNET_break (0);
     95     return GNUNET_SYSERR;
     96   }
     97 
     98   for (unsigned int i = 0; i<num_kycs; i++)
     99   {
    100     const json_t *jlimits = NULL;
    101     const json_t *jkycauths = NULL;
    102     struct GNUNET_JSON_Specification spec[] = {
    103       GNUNET_JSON_spec_mark_optional (
    104         GNUNET_JSON_spec_array_const (
    105           "limits",
    106           &jlimits),
    107         NULL),
    108       GNUNET_JSON_spec_mark_optional (
    109         GNUNET_JSON_spec_array_const (
    110           "payto_kycauths",
    111           &jkycauths),
    112         NULL),
    113       GNUNET_JSON_spec_end ()
    114     };
    115 
    116     if (GNUNET_OK !=
    117         GNUNET_JSON_parse (json_array_get (jkyc,
    118                                            i),
    119                            spec,
    120                            NULL, NULL))
    121     {
    122       GNUNET_break (0);
    123       json_dumpf (json_array_get (jkyc,
    124                                   i),
    125                   stderr,
    126                   JSON_INDENT (2));
    127       return GNUNET_SYSERR;
    128     }
    129     num_limits += json_array_size (jlimits);
    130     num_kycauths += json_array_size (jkycauths);
    131   }
    132 
    133 
    134   {
    135     struct TALER_MERCHANT_AccountKycRedirectDetail kycs[
    136       GNUNET_NZL (num_kycs)];
    137     struct TALER_EXCHANGE_AccountLimit limits[
    138       GNUNET_NZL (num_limits)];
    139     struct TALER_FullPayto payto_kycauths[
    140       GNUNET_NZL (num_kycauths)];
    141 
    142     memset (kycs,
    143             0,
    144             sizeof (kycs));
    145     for (unsigned int i = 0; i<num_kycs; i++)
    146     {
    147       struct TALER_MERCHANT_AccountKycRedirectDetail *rd
    148         = &kycs[i];
    149       const json_t *jlimits = NULL;
    150       const json_t *jkycauths = NULL;
    151       uint32_t hs;
    152       struct GNUNET_JSON_Specification spec[] = {
    153         TALER_JSON_spec_full_payto_uri (
    154           "payto_uri",
    155           &rd->payto_uri),
    156         TALER_JSON_spec_web_url (
    157           "exchange_url",
    158           &rd->exchange_url),
    159         GNUNET_JSON_spec_uint32 (
    160           "exchange_http_status",
    161           &hs),
    162         GNUNET_JSON_spec_bool (
    163           "no_keys",
    164           &rd->no_keys),
    165         GNUNET_JSON_spec_bool (
    166           "auth_conflict",
    167           &rd->auth_conflict),
    168         GNUNET_JSON_spec_mark_optional (
    169           TALER_JSON_spec_ec (
    170             "exchange_code",
    171             &rd->exchange_code),
    172           NULL),
    173         GNUNET_JSON_spec_mark_optional (
    174           GNUNET_JSON_spec_fixed_auto (
    175             "access_token",
    176             &rd->access_token),
    177           &rd->no_access_token),
    178         GNUNET_JSON_spec_mark_optional (
    179           GNUNET_JSON_spec_array_const (
    180             "limits",
    181             &jlimits),
    182           NULL),
    183         GNUNET_JSON_spec_mark_optional (
    184           GNUNET_JSON_spec_array_const (
    185             "payto_kycauths",
    186             &jkycauths),
    187           NULL),
    188         GNUNET_JSON_spec_end ()
    189       };
    190       size_t j;
    191       json_t *jlimit;
    192       json_t *jkycauth;
    193 
    194       if (GNUNET_OK !=
    195           GNUNET_JSON_parse (json_array_get (jkyc,
    196                                              i),
    197                              spec,
    198                              NULL, NULL))
    199       {
    200         GNUNET_break (0);
    201         json_dumpf (json_array_get (jkyc,
    202                                     i),
    203                     stderr,
    204                     JSON_INDENT (2));
    205         return GNUNET_SYSERR;
    206       }
    207       rd->exchange_http_status = (unsigned int) hs;
    208       rd->limits = &limits[pos_limits];
    209       rd->limits_length = json_array_size (jlimits);
    210       json_array_foreach (jlimits, j, jlimit)
    211       {
    212         struct TALER_EXCHANGE_AccountLimit *limit
    213           = &limits[pos_limits];
    214         struct GNUNET_JSON_Specification jspec[] = {
    215           TALER_JSON_spec_kycte (
    216             "operation_type",
    217             &limit->operation_type),
    218           GNUNET_JSON_spec_relative_time (
    219             "timeframe",
    220             &limit->timeframe),
    221           TALER_JSON_spec_amount_any (
    222             "threshold",
    223             &limit->threshold),
    224           GNUNET_JSON_spec_mark_optional (
    225             GNUNET_JSON_spec_bool (
    226               "soft_limit",
    227               &limit->soft_limit),
    228             NULL),
    229           GNUNET_JSON_spec_end ()
    230         };
    231 
    232         GNUNET_assert (pos_limits < num_limits);
    233         limit->soft_limit = false;
    234         if (GNUNET_OK !=
    235             GNUNET_JSON_parse (jlimit,
    236                                jspec,
    237                                NULL, NULL))
    238         {
    239           GNUNET_break (0);
    240           json_dumpf (json_array_get (jkyc,
    241                                       i),
    242                       stderr,
    243                       JSON_INDENT (2));
    244           return GNUNET_SYSERR;
    245         }
    246         pos_limits++;
    247       }
    248       rd->payto_kycauths = &payto_kycauths[pos_kycauths];
    249       rd->pkycauth_length = json_array_size (jkycauths);
    250       json_array_foreach (jkycauths, j, jkycauth)
    251       {
    252         GNUNET_assert (pos_kycauths < num_kycauths);
    253         payto_kycauths[pos_kycauths].full_payto
    254           = (char *) json_string_value (jkycauth);
    255         if (NULL == payto_kycauths[pos_kycauths].full_payto)
    256         {
    257           GNUNET_break (0);
    258           json_dumpf (json_array_get (jkyc,
    259                                       i),
    260                       stderr,
    261                       JSON_INDENT (2));
    262           return GNUNET_SYSERR;
    263         }
    264         pos_kycauths++;
    265       }
    266     }
    267     kr->details.ok.kycs = kycs;
    268     kr->details.ok.kycs_length = num_kycs;
    269     kyc->cb (kyc->cb_cls,
    270              kr);
    271   }
    272   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 handle_get_kyc_finished (void *cls,
    286                          long response_code,
    287                          const void *response)
    288 {
    289   struct TALER_MERCHANT_KycGetHandle *kyc = cls;
    290   const json_t *json = response;
    291   struct TALER_MERCHANT_KycResponse kr = {
    292     .hr.http_status = (unsigned int) response_code,
    293     .hr.reply = json
    294   };
    295 
    296   kyc->job = NULL;
    297   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    298               "Got /kyc response with status code %u\n",
    299               (unsigned int) response_code);
    300   switch (response_code)
    301   {
    302   case MHD_HTTP_OK:
    303     {
    304       const json_t *jkyc;
    305       struct GNUNET_JSON_Specification spec[] = {
    306         GNUNET_JSON_spec_array_const ("kyc_data",
    307                                       &jkyc),
    308         GNUNET_JSON_spec_end ()
    309       };
    310 
    311       if (GNUNET_OK !=
    312           GNUNET_JSON_parse (json,
    313                              spec,
    314                              NULL, NULL))
    315       {
    316         kr.hr.http_status = 0;
    317         kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    318         break;
    319       }
    320       if (GNUNET_OK !=
    321           parse_kyc (kyc,
    322                      &kr,
    323                      jkyc))
    324       {
    325         kr.hr.http_status = 0;
    326         kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    327         break;
    328       }
    329       /* parse_kyc called the continuation already */
    330       TALER_MERCHANT_kyc_get_cancel (kyc);
    331       return;
    332     }
    333   case MHD_HTTP_NO_CONTENT:
    334     break;
    335   case MHD_HTTP_UNAUTHORIZED:
    336     kr.hr.ec = TALER_JSON_get_error_code (json);
    337     kr.hr.hint = TALER_JSON_get_error_hint (json);
    338     /* Nothing really to verify, merchant says we need to authenticate. */
    339     break;
    340   case MHD_HTTP_SERVICE_UNAVAILABLE:
    341     break;
    342   default:
    343     /* unexpected response code */
    344     kr.hr.ec = TALER_JSON_get_error_code (json);
    345     kr.hr.hint = TALER_JSON_get_error_hint (json);
    346     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    347                 "Unexpected response code %u/%d\n",
    348                 (unsigned int) response_code,
    349                 (int) kr.hr.ec);
    350     break;
    351   }
    352   kyc->cb (kyc->cb_cls,
    353            &kr);
    354   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 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   kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle);
    389   kyc->ctx = ctx;
    390   kyc->cb = cb;
    391   kyc->cb_cls = cb_cls;
    392   GNUNET_snprintf (lpt_str,
    393                    sizeof (lpt_str),
    394                    "%d",
    395                    (int) lpt);
    396   tms = timeout.rel_value_us
    397         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    398   GNUNET_snprintf (timeout_ms,
    399                    sizeof (timeout_ms),
    400                    "%llu",
    401                    tms);
    402   kyc->url
    403     = TALER_url_join (
    404         url,
    405         "kyc",
    406         "h_wire",
    407         NULL == h_wire
    408         ? NULL
    409         : GNUNET_h2s_full (&h_wire->hash),
    410         "exchange_url",
    411         NULL == exchange_url
    412         ? NULL
    413         : exchange_url,
    414         "timeout_ms",
    415         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   GNUNET_free (url);
    424   if (NULL == kyc->url)
    425   {
    426     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    427                 "Could not construct request URL.\n");
    428     GNUNET_free (kyc);
    429     return NULL;
    430   }
    431   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    432               "Requesting URL '%s'\n",
    433               kyc->url);
    434   eh = TALER_MERCHANT_curl_easy_get_ (kyc->url);
    435   if (0 != tms)
    436   {
    437     GNUNET_break (CURLE_OK ==
    438                   curl_easy_setopt (eh,
    439                                     CURLOPT_TIMEOUT_MS,
    440                                     (long) (tms + 100L)));
    441   }
    442   kyc->job
    443     = GNUNET_CURL_job_add (ctx,
    444                            eh,
    445                            &handle_get_kyc_finished,
    446                            kyc);
    447   return kyc;
    448 }
    449 
    450 
    451 struct TALER_MERCHANT_KycGetHandle *
    452 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   GNUNET_asprintf (&url,
    465                    "%sprivate/",
    466                    backend_url);
    467   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 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   GNUNET_asprintf (&url,
    493                    "%smanagement/instances/%s/",
    494                    backend_url,
    495                    instance_id);
    496   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 TALER_MERCHANT_kyc_get_cancel (
    509   struct TALER_MERCHANT_KycGetHandle *kyc)
    510 {
    511   if (NULL != kyc->job)
    512     GNUNET_CURL_job_cancel (kyc->job);
    513   GNUNET_free (kyc->url);
    514   GNUNET_free (kyc);
    515 }