merchant

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

merchant_api_get-fountain-info.c (10287B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Lesser General Public License as
      7   published by the Free Software Foundation; either version 2.1,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU Lesser General Public License for more details.
     14 
     15   You should have received a copy of the GNU Lesser General
     16   Public License along with TALER; see the file COPYING.LGPL.
     17   If not, see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file src/lib/merchant_api_get-fountain-info.c
     21  * @brief Implementation of the (public) GET /fountain/info request
     22  * @author Bohdan Potuzhnyi
     23  */
     24 #include "platform.h"
     25 #include <curl/curl.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h> /* just for HTTP status codes */
     28 #include <gnunet/gnunet_util_lib.h>
     29 #include <gnunet/gnunet_curl_lib.h>
     30 #include <taler/merchant/get-fountain-info.h>
     31 #include "merchant_api_curl_defaults.h"
     32 #include "merchant_api_common.h"
     33 #include <taler/taler_json_lib.h>
     34 #include <taler/taler_curl_lib.h>
     35 
     36 
     37 /**
     38  * Handle for a GET /fountain/info operation.
     39  */
     40 struct TALER_MERCHANT_GetFountainInfoHandle
     41 {
     42   /**
     43    * Base URL of the merchant backend.
     44    */
     45   char *base_url;
     46 
     47   /**
     48    * The full URL for this request.
     49    */
     50   char *url;
     51 
     52   /**
     53    * HTTP headers (Authorization) for the request.
     54    */
     55   struct curl_slist *headers;
     56 
     57   /**
     58    * Handle for the request.
     59    */
     60   struct GNUNET_CURL_Job *job;
     61 
     62   /**
     63    * Function to call with the result.
     64    */
     65   TALER_MERCHANT_GetFountainInfoCallback cb;
     66 
     67   /**
     68    * Closure for @a cb.
     69    */
     70   TALER_MERCHANT_GET_FOUNTAIN_INFO_RESULT_CLOSURE *cb_cls;
     71 
     72   /**
     73    * Reference to the execution context.
     74    */
     75   struct GNUNET_CURL_Context *ctx;
     76 };
     77 
     78 
     79 /**
     80  * Parse the fountain info and invoke the callback.
     81  *
     82  * @param gfih operation handle
     83  * @param[in,out] fir response to complete and pass to the callback
     84  */
     85 static void
     86 handle_ok (struct TALER_MERCHANT_GetFountainInfoHandle *gfih,
     87            struct TALER_MERCHANT_GetFountainInfoResponse *fir)
     88 {
     89   const json_t *jgrants;
     90   struct GNUNET_JSON_Specification spec[] = {
     91     GNUNET_JSON_spec_relative_time ("poll_freq",
     92                                     &fir->details.ok.poll_freq),
     93     GNUNET_JSON_spec_array_const ("grants",
     94                                   &jgrants),
     95     GNUNET_JSON_spec_end ()
     96   };
     97 
     98   if (GNUNET_OK !=
     99       GNUNET_JSON_parse (fir->hr.reply,
    100                          spec,
    101                          NULL,
    102                          NULL))
    103   {
    104     GNUNET_break_op (0);
    105     fir->hr.http_status = 0;
    106     fir->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    107     gfih->cb (gfih->cb_cls,
    108               fir);
    109     return;
    110   }
    111   if (json_array_size (jgrants) > TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS)
    112   {
    113     fir->hr.http_status = 0;
    114     fir->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    115     gfih->cb (gfih->cb_cls,
    116               fir);
    117     return;
    118   }
    119   {
    120     unsigned int len = (unsigned int) json_array_size (jgrants);
    121     struct TALER_MERCHANT_FountainWalletGrant *grants
    122       = GNUNET_new_array (GNUNET_NZL (len),
    123                           struct TALER_MERCHANT_FountainWalletGrant);
    124     unsigned int parsed = 0;
    125     bool fail = false;
    126     size_t idx;
    127     json_t *jg;
    128 
    129     json_array_foreach ((json_t *) jgrants, idx, jg)
    130     {
    131       struct TALER_MERCHANT_FountainWalletGrant *wg = &grants[idx];
    132       const json_t *jfamily;
    133       uint32_t window;
    134       struct GNUNET_JSON_Specification ispec[] = {
    135         GNUNET_JSON_spec_string ("token_family_slug",
    136                                  &wg->grant.token_family_slug),
    137         GNUNET_JSON_spec_uint64 ("tokens_per_period_limit",
    138                                  &wg->grant.tokens_per_period_limit),
    139         GNUNET_JSON_spec_uint64 ("tokens_per_period_stash",
    140                                  &wg->grant.tokens_per_period_stash),
    141         GNUNET_JSON_spec_uint32 ("key_window_size",
    142                                  &window),
    143         GNUNET_JSON_spec_object_const ("token_family",
    144                                        &jfamily),
    145         GNUNET_JSON_spec_end ()
    146       };
    147 
    148       if (GNUNET_OK !=
    149           GNUNET_JSON_parse (jg,
    150                              ispec,
    151                              NULL,
    152                              NULL))
    153       {
    154         GNUNET_break_op (0);
    155         fail = true;
    156         break;
    157       }
    158       wg->grant.key_window_size = window;
    159       /* The single-family parser expects an object mapping slugs to
    160          families (as in contract terms); wrap accordingly. */
    161       {
    162         json_t *jwrap;
    163         struct TALER_MERCHANT_ContractTokenFamily *families = NULL;
    164         unsigned int families_len = 0;
    165         struct GNUNET_JSON_Specification wspec[] = {
    166           TALER_MERCHANT_spec_token_families (NULL,
    167                                               &families,
    168                                               &families_len),
    169           GNUNET_JSON_spec_end ()
    170         };
    171 
    172         jwrap = GNUNET_JSON_PACK (
    173           GNUNET_JSON_pack_object_incref (
    174             wg->grant.token_family_slug,
    175             (json_t *) jfamily));
    176         if ( (GNUNET_OK !=
    177               GNUNET_JSON_parse (jwrap,
    178                                  wspec,
    179                                  NULL,
    180                                  NULL)) ||
    181              (1 != families_len) )
    182         {
    183           GNUNET_break_op (0);
    184           json_decref (jwrap);
    185           for (unsigned int i = 0; i < families_len; i++)
    186             TALER_MERCHANT_contract_token_family_free (&families[i]);
    187           GNUNET_free (families);
    188           fail = true;
    189           break;
    190         }
    191         json_decref (jwrap);
    192         wg->token_family = families[0];
    193         GNUNET_free (families);
    194       }
    195       parsed++;
    196     }
    197     if (fail)
    198     {
    199       for (unsigned int i = 0; i < parsed; i++)
    200         TALER_MERCHANT_contract_token_family_free (&grants[i].token_family);
    201       GNUNET_free (grants);
    202       fir->hr.http_status = 0;
    203       fir->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    204       gfih->cb (gfih->cb_cls,
    205                 fir);
    206       return;
    207     }
    208     fir->details.ok.grants = grants;
    209     fir->details.ok.grants_len = len;
    210     gfih->cb (gfih->cb_cls,
    211               fir);
    212     for (unsigned int i = 0; i < len; i++)
    213       TALER_MERCHANT_contract_token_family_free (&grants[i].token_family);
    214     GNUNET_free (grants);
    215   }
    216 }
    217 
    218 
    219 /**
    220  * Function called when we're done processing the
    221  * HTTP GET /fountain/info request.
    222  *
    223  * @param cls the `struct TALER_MERCHANT_GetFountainInfoHandle`
    224  * @param response_code HTTP response code, 0 on error
    225  * @param response response body, NULL if not in JSON
    226  */
    227 static void
    228 handle_get_fountain_info_finished (void *cls,
    229                                    long response_code,
    230                                    const void *response)
    231 {
    232   struct TALER_MERCHANT_GetFountainInfoHandle *gfih = cls;
    233   const json_t *json = response;
    234   struct TALER_MERCHANT_GetFountainInfoResponse fir = {
    235     .hr.http_status = (unsigned int) response_code,
    236     .hr.reply = json
    237   };
    238 
    239   gfih->job = NULL;
    240   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    241               "GET /fountain/info completed with response code %u\n",
    242               (unsigned int) response_code);
    243   switch (response_code)
    244   {
    245   case 0:
    246     fir.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    247     break;
    248   case MHD_HTTP_OK:
    249     handle_ok (gfih,
    250                &fir);
    251     TALER_MERCHANT_get_fountain_info_cancel (gfih);
    252     return;
    253   case MHD_HTTP_UNAUTHORIZED:
    254   case MHD_HTTP_NOT_FOUND:
    255   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    256     fir.hr.ec = TALER_JSON_get_error_code (json);
    257     fir.hr.hint = TALER_JSON_get_error_hint (json);
    258     break;
    259   default:
    260     TALER_MERCHANT_parse_error_details_ (json,
    261                                          response_code,
    262                                          &fir.hr);
    263     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    264                 "Unexpected response code %u/%d\n",
    265                 (unsigned int) response_code,
    266                 (int) fir.hr.ec);
    267     GNUNET_break_op (0);
    268     break;
    269   }
    270   gfih->cb (gfih->cb_cls,
    271             &fir);
    272   TALER_MERCHANT_get_fountain_info_cancel (gfih);
    273 }
    274 
    275 
    276 struct TALER_MERCHANT_GetFountainInfoHandle *
    277 TALER_MERCHANT_get_fountain_info_create (
    278   struct GNUNET_CURL_Context *ctx,
    279   const char *url,
    280   const char *fountain_secret)
    281 {
    282   struct TALER_MERCHANT_GetFountainInfoHandle *gfih;
    283 
    284   gfih = GNUNET_new (struct TALER_MERCHANT_GetFountainInfoHandle);
    285   gfih->ctx = ctx;
    286   gfih->base_url = GNUNET_strdup (url);
    287   {
    288     char *hdr;
    289 
    290     GNUNET_asprintf (&hdr,
    291                      "%s: Bearer %s",
    292                      MHD_HTTP_HEADER_AUTHORIZATION,
    293                      fountain_secret);
    294     gfih->headers = curl_slist_append (NULL,
    295                                        hdr);
    296     GNUNET_free (hdr);
    297   }
    298   return gfih;
    299 }
    300 
    301 
    302 enum TALER_ErrorCode
    303 TALER_MERCHANT_get_fountain_info_start (
    304   struct TALER_MERCHANT_GetFountainInfoHandle *gfih,
    305   TALER_MERCHANT_GetFountainInfoCallback cb,
    306   TALER_MERCHANT_GET_FOUNTAIN_INFO_RESULT_CLOSURE *cb_cls)
    307 {
    308   CURL *eh;
    309 
    310   gfih->cb = cb;
    311   gfih->cb_cls = cb_cls;
    312   gfih->url = TALER_url_join (gfih->base_url,
    313                               "fountain/info",
    314                               NULL);
    315   if (NULL == gfih->url)
    316     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    317   eh = TALER_MERCHANT_curl_easy_get_ (gfih->url);
    318   if (NULL == eh)
    319   {
    320     GNUNET_break (0);
    321     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    322   }
    323   gfih->job = GNUNET_CURL_job_add2 (gfih->ctx,
    324                                     eh,
    325                                     gfih->headers,
    326                                     &handle_get_fountain_info_finished,
    327                                     gfih);
    328   if (NULL == gfih->job)
    329     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    330   return TALER_EC_NONE;
    331 }
    332 
    333 
    334 void
    335 TALER_MERCHANT_get_fountain_info_cancel (
    336   struct TALER_MERCHANT_GetFountainInfoHandle *gfih)
    337 {
    338   if (NULL != gfih->job)
    339   {
    340     GNUNET_CURL_job_cancel (gfih->job);
    341     gfih->job = NULL;
    342   }
    343   curl_slist_free_all (gfih->headers);
    344   GNUNET_free (gfih->url);
    345   GNUNET_free (gfih->base_url);
    346   GNUNET_free (gfih);
    347 }
    348 
    349 
    350 /* end of merchant_api_get-fountain-info.c */