merchant

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

merchant_api_post-fountain-withdraw.c (12233B)


      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_post-fountain-withdraw.c
     21  * @brief Implementation of the (public) POST /fountain/withdraw 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/post-fountain-withdraw.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 POST /fountain/withdraw operation.
     39  */
     40 struct TALER_MERCHANT_PostFountainWithdrawHandle
     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    * Handle for the request.
     54    */
     55   struct GNUNET_CURL_Job *job;
     56 
     57   /**
     58    * Function to call with the result.
     59    */
     60   TALER_MERCHANT_PostFountainWithdrawCallback cb;
     61 
     62   /**
     63    * Closure for @a cb.
     64    */
     65   TALER_MERCHANT_POST_FOUNTAIN_WITHDRAW_RESULT_CLOSURE *cb_cls;
     66 
     67   /**
     68    * Reference to the execution context.
     69    */
     70   struct GNUNET_CURL_Context *ctx;
     71 
     72   /**
     73    * Minor context that holds body and headers.
     74    */
     75   struct TALER_CURL_PostContext post_ctx;
     76 
     77   /**
     78    * Request body.
     79    */
     80   json_t *req_obj;
     81 };
     82 
     83 
     84 /**
     85  * Parse the withdraw results and invoke the callback.
     86  *
     87  * @param pfwh operation handle
     88  * @param[in,out] fwr response to complete and pass to the callback
     89  */
     90 static void
     91 handle_ok (struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh,
     92            struct TALER_MERCHANT_PostFountainWithdrawResponse *fwr)
     93 {
     94   const json_t *jresults;
     95   struct GNUNET_JSON_Specification spec[] = {
     96     GNUNET_JSON_spec_array_const ("grants",
     97                                   &jresults),
     98     GNUNET_JSON_spec_end ()
     99   };
    100 
    101   if (GNUNET_OK !=
    102       GNUNET_JSON_parse (fwr->hr.reply,
    103                          spec,
    104                          NULL,
    105                          NULL))
    106   {
    107     GNUNET_break_op (0);
    108     fwr->hr.http_status = 0;
    109     fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    110     pfwh->cb (pfwh->cb_cls,
    111               fwr);
    112     return;
    113   }
    114   if (json_array_size (jresults) > TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS)
    115   {
    116     fwr->hr.http_status = 0;
    117     fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    118     pfwh->cb (pfwh->cb_cls,
    119               fwr);
    120     return;
    121   }
    122   {
    123     unsigned int len = (unsigned int) json_array_size (jresults);
    124     struct TALER_MERCHANT_FountainWithdrawResult *results;
    125     struct TALER_BlindedTokenIssueSignature *all_sigs = NULL;
    126     unsigned int num_all_sigs = 0;
    127     bool fail = false;
    128     size_t idx;
    129     json_t *jr;
    130 
    131     /* Bound the total before adding, narrowing or allocating. */
    132     json_array_foreach ((json_t *) jresults, idx, jr)
    133     {
    134       const json_t *jsigs = json_object_get (jr,
    135                                              "token_sigs");
    136 
    137       if ( (! json_is_array (jsigs)) ||
    138            (json_array_size (jsigs) >
    139             TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS - num_all_sigs) )
    140       {
    141         fwr->hr.http_status = 0;
    142         fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    143         pfwh->cb (pfwh->cb_cls,
    144                   fwr);
    145         return;
    146       }
    147       num_all_sigs += (unsigned int) json_array_size (jsigs);
    148     }
    149     results = GNUNET_new_array (GNUNET_NZL (len),
    150                                 struct TALER_MERCHANT_FountainWithdrawResult);
    151     all_sigs = GNUNET_new_array (GNUNET_NZL (num_all_sigs),
    152                                  struct TALER_BlindedTokenIssueSignature);
    153     num_all_sigs = 0;
    154     json_array_foreach ((json_t *) jresults, idx, jr)
    155     {
    156       struct TALER_MERCHANT_FountainWithdrawResult *res = &results[idx];
    157       const json_t *jsigs;
    158       struct GNUNET_JSON_Specification ispec[] = {
    159         GNUNET_JSON_spec_string ("token_family_slug",
    160                                  &res->token_family_slug),
    161         GNUNET_JSON_spec_fixed_auto ("h_issue",
    162                                      &res->h_issue),
    163         GNUNET_JSON_spec_array_const ("token_sigs",
    164                                       &jsigs),
    165         GNUNET_JSON_spec_end ()
    166       };
    167 
    168       if (GNUNET_OK !=
    169           GNUNET_JSON_parse (jr,
    170                              ispec,
    171                              NULL,
    172                              NULL))
    173       {
    174         GNUNET_break_op (0);
    175         fail = true;
    176         break;
    177       }
    178       res->token_sigs = &all_sigs[num_all_sigs];
    179       res->num_sigs = (unsigned int) json_array_size (jsigs);
    180       {
    181         size_t sidx;
    182         json_t *js;
    183 
    184         json_array_foreach ((json_t *) jsigs, sidx, js)
    185         {
    186           struct GNUNET_JSON_Specification sspec[] = {
    187             GNUNET_JSON_spec_blinded_signature (
    188               "blind_sig",
    189               &all_sigs[num_all_sigs].signature),
    190             GNUNET_JSON_spec_end ()
    191           };
    192 
    193           if (GNUNET_OK !=
    194               GNUNET_JSON_parse (js,
    195                                  sspec,
    196                                  NULL,
    197                                  NULL))
    198           {
    199             GNUNET_break_op (0);
    200             fail = true;
    201             break;
    202           }
    203           num_all_sigs++;
    204         }
    205       }
    206       if (fail)
    207         break;
    208     }
    209     if (fail)
    210     {
    211       fwr->hr.http_status = 0;
    212       fwr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    213       pfwh->cb (pfwh->cb_cls,
    214                 fwr);
    215     }
    216     else
    217     {
    218       fwr->details.ok.results = results;
    219       fwr->details.ok.results_len = len;
    220       pfwh->cb (pfwh->cb_cls,
    221                 fwr);
    222     }
    223     for (unsigned int i = 0; i < num_all_sigs; i++)
    224       if (NULL != all_sigs[i].signature)
    225         GNUNET_CRYPTO_blinded_sig_decref (all_sigs[i].signature);
    226     GNUNET_free (all_sigs);
    227     GNUNET_free (results);
    228   }
    229 }
    230 
    231 
    232 /**
    233  * Function called when we're done processing the
    234  * HTTP POST /fountain/withdraw request.
    235  *
    236  * @param cls the `struct TALER_MERCHANT_PostFountainWithdrawHandle`
    237  * @param response_code HTTP response code, 0 on error
    238  * @param response response body, NULL if not in JSON
    239  */
    240 static void
    241 handle_post_fountain_withdraw_finished (void *cls,
    242                                         long response_code,
    243                                         const void *response)
    244 {
    245   struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh = cls;
    246   const json_t *json = response;
    247   struct TALER_MERCHANT_PostFountainWithdrawResponse fwr = {
    248     .hr.http_status = (unsigned int) response_code,
    249     .hr.reply = json
    250   };
    251 
    252   pfwh->job = NULL;
    253   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    254               "POST /fountain/withdraw completed with response code %u\n",
    255               (unsigned int) response_code);
    256   switch (response_code)
    257   {
    258   case 0:
    259     fwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    260     break;
    261   case MHD_HTTP_OK:
    262     handle_ok (pfwh,
    263                &fwr);
    264     TALER_MERCHANT_post_fountain_withdraw_cancel (pfwh);
    265     return;
    266   case MHD_HTTP_BAD_REQUEST:
    267   case MHD_HTTP_UNAUTHORIZED:
    268   case MHD_HTTP_NOT_FOUND:
    269   case MHD_HTTP_CONFLICT:
    270   case MHD_HTTP_TOO_MANY_REQUESTS:
    271   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    272     fwr.hr.ec = TALER_JSON_get_error_code (json);
    273     fwr.hr.hint = TALER_JSON_get_error_hint (json);
    274     break;
    275   default:
    276     TALER_MERCHANT_parse_error_details_ (json,
    277                                          response_code,
    278                                          &fwr.hr);
    279     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    280                 "Unexpected response code %u/%d\n",
    281                 (unsigned int) response_code,
    282                 (int) fwr.hr.ec);
    283     GNUNET_break_op (0);
    284     break;
    285   }
    286   pfwh->cb (pfwh->cb_cls,
    287             &fwr);
    288   TALER_MERCHANT_post_fountain_withdraw_cancel (pfwh);
    289 }
    290 
    291 
    292 struct TALER_MERCHANT_PostFountainWithdrawHandle *
    293 TALER_MERCHANT_post_fountain_withdraw_create (
    294   struct GNUNET_CURL_Context *ctx,
    295   const char *url,
    296   const char *fountain_secret,
    297   unsigned int num_entries,
    298   const struct TALER_MERCHANT_FountainWithdrawEntry *entries)
    299 {
    300   struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh;
    301   json_t *jentries;
    302 
    303   jentries = json_array ();
    304   GNUNET_assert (NULL != jentries);
    305   for (unsigned int i = 0; i < num_entries; i++)
    306   {
    307     const struct TALER_MERCHANT_FountainWithdrawEntry *we = &entries[i];
    308     json_t *jenvelopes;
    309 
    310     jenvelopes = json_array ();
    311     GNUNET_assert (NULL != jenvelopes);
    312     for (unsigned int j = 0; j < we->num_envelopes; j++)
    313       GNUNET_assert (0 ==
    314                      json_array_append_new (
    315                        jenvelopes,
    316                        GNUNET_JSON_PACK (
    317                          TALER_JSON_pack_token_envelope (NULL,
    318                                                          &we->envelopes[j]))));
    319     GNUNET_assert (0 ==
    320                    json_array_append_new (
    321                      jentries,
    322                      GNUNET_JSON_PACK (
    323                        GNUNET_JSON_pack_string ("token_family_slug",
    324                                                 we->token_family_slug),
    325                        GNUNET_JSON_pack_allow_null (
    326                          GNUNET_TIME_absolute_is_zero (
    327                            we->valid_at.abs_time)
    328                          ? GNUNET_JSON_pack_string ("valid_at",
    329                                                     NULL)
    330                          : GNUNET_JSON_pack_timestamp ("valid_at",
    331                                                        we->valid_at)),
    332                        GNUNET_JSON_pack_array_steal ("envelopes",
    333                                                      jenvelopes))));
    334   }
    335   pfwh = GNUNET_new (struct TALER_MERCHANT_PostFountainWithdrawHandle);
    336   pfwh->ctx = ctx;
    337   pfwh->base_url = GNUNET_strdup (url);
    338   pfwh->req_obj = GNUNET_JSON_PACK (
    339     GNUNET_JSON_pack_string ("fountain_secret",
    340                              fountain_secret),
    341     GNUNET_JSON_pack_array_steal ("grants",
    342                                   jentries));
    343   return pfwh;
    344 }
    345 
    346 
    347 enum TALER_ErrorCode
    348 TALER_MERCHANT_post_fountain_withdraw_start (
    349   struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh,
    350   TALER_MERCHANT_PostFountainWithdrawCallback cb,
    351   TALER_MERCHANT_POST_FOUNTAIN_WITHDRAW_RESULT_CLOSURE *cb_cls)
    352 {
    353   CURL *eh;
    354 
    355   pfwh->cb = cb;
    356   pfwh->cb_cls = cb_cls;
    357   pfwh->url = TALER_url_join (pfwh->base_url,
    358                               "fountain/withdraw",
    359                               NULL);
    360   if (NULL == pfwh->url)
    361     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    362   eh = TALER_MERCHANT_curl_easy_get_ (pfwh->url);
    363   if ( (NULL == eh) ||
    364        (GNUNET_OK !=
    365         TALER_curl_easy_post (&pfwh->post_ctx,
    366                               eh,
    367                               pfwh->req_obj)) )
    368   {
    369     GNUNET_break (0);
    370     if (NULL != eh)
    371       curl_easy_cleanup (eh);
    372     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    373   }
    374   pfwh->job = GNUNET_CURL_job_add2 (pfwh->ctx,
    375                                     eh,
    376                                     pfwh->post_ctx.headers,
    377                                     &handle_post_fountain_withdraw_finished,
    378                                     pfwh);
    379   if (NULL == pfwh->job)
    380     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    381   return TALER_EC_NONE;
    382 }
    383 
    384 
    385 void
    386 TALER_MERCHANT_post_fountain_withdraw_cancel (
    387   struct TALER_MERCHANT_PostFountainWithdrawHandle *pfwh)
    388 {
    389   if (NULL != pfwh->job)
    390   {
    391     GNUNET_CURL_job_cancel (pfwh->job);
    392     pfwh->job = NULL;
    393   }
    394   TALER_curl_easy_post_finished (&pfwh->post_ctx);
    395   json_decref (pfwh->req_obj);
    396   GNUNET_free (pfwh->url);
    397   GNUNET_free (pfwh->base_url);
    398   GNUNET_free (pfwh);
    399 }
    400 
    401 
    402 /* end of merchant_api_post-fountain-withdraw.c */