cash2ecash

cash2ecash: cash acceptor that issues digital cash (experimental)
Log | Files | Refs | README | LICENSE

bank_api_post_accounts_withdrawals.c (11160B)


      1 /*
      2   This file is part of TALER cash2ecash
      3   Copyright (C) 2026 GNUnet e.V.
      4 
      5   This program is free software: you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation, either version 3 of the
      8   License, or (at your option) any later version.
      9 
     10   This program is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14   GNU Affero General Public License for more details.
     15 
     16   You should have received a copy of the GNU Affero General Public License
     17   along with this program.  If not, see <https://www.gnu.org/licenses/>.
     18 */
     19 /**
     20  * @file lib/bank_api_post_accounts_withdrawals.c
     21  * @brief implements the Taler Bank API "POST accounts/$USERNAME/withdrawals" handler
     22  * @author Reto Tellenbach
     23  */
     24  
     25 #include <microhttpd.h>
     26 #include "taler/taler_json_lib.h"
     27 #include "bank_api_post_accounts_withdrawals.h"
     28 
     29 /**
     30  * Log error related to CURL operations.
     31  *
     32  * @param type log level
     33  * @param function which function failed to run
     34  * @param code what was the curl error code
     35  */
     36 #define CURL_STRERROR(type, function, code)      \
     37         GNUNET_log (type, \
     38                     "Curl function `%s' has failed at `%s:%d' with error: %s", \
     39                     function, __FILE__, __LINE__, curl_easy_strerror (code));
     40 
     41 /**
     42  * Handle for the accounts create withdrawal request.
     43  */
     44 struct TALER_BANK_PostCreateWithdrawalHandle
     45 {
     46   /**
     47    * The context of this handle
     48    */
     49   struct GNUNET_CURL_Context *ctx;
     50 
     51   /**
     52    * curle easy handle
     53    */
     54   CURL *easy_handle;
     55 
     56   /**
     57    * Context for curl easy post. Keeps the data that must
     58    * persist for Curl to make the upload.
     59    */
     60   struct TALER_CURL_PostContext post_ctx;
     61 
     62   /**
     63    * Authentification date to access bank account
     64    */
     65   const struct DIGITIZER_BankAuthenticationData *authorization;
     66 
     67   /**
     68    * Function to call with the ,
     69    * NULL if this has already been done.
     70    */
     71   TALER_BANK_CreateWithdrawalCallback woc_cb;
     72 
     73   /**
     74    * Closure to pass to
     75    * ater withdrawal operation creation
     76    */
     77   void *woc_cb_cls;
     78 
     79   /**
     80    * Data for the request to get the accounts/$USERNAME of a bank,
     81    * NULL once we are past stage #MHS_INIT.
     82    */
     83   struct GNUNET_CURL_Job *job;
     84 
     85   /**
     86    * The whole request line
     87    */
     88   char *job_url;
     89 
     90 };
     91 
     92 
     93 /**
     94  * Decode the JSON in @a resp_obj from the accounts/$USERNAME/withdrawal response
     95  *
     96  * @param[in] resp_obj JSON object to parse
     97  * @param[in,out] vi where to store the results we decoded
     98  * @param[out] vc where to store account info data
     99  * @return #TALER_EC_NONE on success
    100  */
    101 static enum TALER_ErrorCode
    102 decode_config_json (const json_t *resp_obj,
    103                     struct TALER_BANK_CreateWithdrawalInformatio *vi)
    104 {
    105   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    106               "Received body\n`%s'\n",
    107               json_dumps(resp_obj, 0));
    108   
    109 
    110   struct GNUNET_JSON_Specification spec[] = {
    111     GNUNET_JSON_spec_string ("withdrawal_id",
    112                               &vi->withdrawal_id),
    113     GNUNET_JSON_spec_string ("taler_withdraw_uri",
    114                               &vi->taler_withdraw_uri),
    115     GNUNET_JSON_spec_end ()
    116   };
    117 
    118   if (JSON_OBJECT != json_typeof (resp_obj))
    119   {
    120     GNUNET_break_op (0);
    121     return TALER_EC_GENERIC_JSON_INVALID;
    122   }
    123   if (GNUNET_OK !=
    124       GNUNET_JSON_parse (resp_obj,
    125                          spec,
    126                          NULL, NULL))
    127   {
    128     GNUNET_break_op (0);
    129     return TALER_EC_GENERIC_JSON_INVALID;
    130   }
    131 
    132   return TALER_EC_NONE;
    133 }
    134 
    135 
    136 /**
    137  * Callback used when http reply arived to a /accounts/$USERNAME/withdrawal request.
    138  *
    139  * @param cls the `struct TALER_BANK_PostCreateWithdrawalHandle`
    140  * @param response_code HTTP response code or 0 on error
    141  * @param  gresp_obj JSON result, NULL on error, must be a `const json_t *`
    142  */
    143 static void
    144 response_cb(void *cls,
    145             long response_code,
    146             const void *gresp_obj)
    147 {
    148     struct TALER_BANK_PostCreateWithdrawalHandle *pcwh = cls;
    149     const json_t *resp_obj = gresp_obj;
    150     
    151     struct TALER_BANK_CreateWithdrawalResponse pacwr = {
    152         .hr.response = resp_obj,
    153         .hr.http_status = (unsigned int)response_code
    154     };
    155     
    156     pcwh->job = NULL; //job was successfull, curl job cancel not needed anymore in cleanup
    157     pcwh->easy_handle = NULL;
    158     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    159               "Received from URL `%s' with status %ld.\n",
    160               pcwh->job_url,
    161               response_code);
    162     
    163     switch (response_code)
    164   {
    165   case 0:
    166     GNUNET_break_op (0);
    167     pacwr.hr.ec = TALER_EC_INVALID;
    168     break;
    169   case MHD_HTTP_OK:
    170     if (NULL == resp_obj)
    171     {
    172       GNUNET_break_op (0);
    173       pacwr.hr.http_status = 0;
    174       pacwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    175       break;
    176     }
    177     pacwr.hr.ec = decode_config_json (resp_obj,
    178                                    &pacwr.details.ok.wopd);
    179     if (TALER_EC_NONE != pacwr.hr.ec)
    180     {
    181       GNUNET_break_op (0);
    182       pacwr.hr.http_status = 0;
    183       break;
    184     }
    185     break;
    186   case MHD_HTTP_UNAUTHORIZED:
    187     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    188     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    189     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    190                 "Invalid or missing credentials %u/%d\n",
    191                 (unsigned int) response_code,
    192                 (int) pacwr.hr.ec);
    193     break;
    194   case MHD_HTTP_FORBIDDEN:
    195     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    196     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    197     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    198                 "Missing rights %u/%d\n",
    199                 (unsigned int) response_code,
    200                 (int) pacwr.hr.ec);
    201     break;
    202   case MHD_HTTP_NOT_FOUND:
    203     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    204     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    205     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    206                 "The account pointed by $USERNAME was not found %u/%d\n",
    207                 (unsigned int) response_code,
    208                 (int) pacwr.hr.ec);
    209     break;
    210   case MHD_HTTP_CONFLICT:
    211     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    212     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    213     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    214                 "The account does not have sufficient funds %u/%d\n",
    215                 (unsigned int) response_code,
    216                 (int) pacwr.hr.ec);
    217     break;
    218   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    219     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    220     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    221     break;
    222   default:
    223     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    224     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    225     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    226                 "Unexpected response code %u/%d\n",
    227                 (unsigned int) response_code,
    228                 (int) pacwr.hr.ec);
    229     break;
    230   }
    231 
    232   pcwh->woc_cb (pcwh->woc_cb_cls, &pacwr);
    233   TALER_BANK_post_withdrawal_create_cancel(pcwh);
    234 }
    235 
    236 /**
    237  * create handle for poost accounts/withdrawal/ request
    238  */
    239 struct TALER_BANK_PostCreateWithdrawalHandle *
    240 TALER_BANK_post_accounts_withdrawal_create ( struct GNUNET_CURL_Context *ctx,
    241                                             const char *base_url,
    242                                             const char *username,
    243                                             const struct DIGITIZER_BankAuthenticationData *authorization)
    244 {
    245   struct TALER_BANK_PostCreateWithdrawalHandle *pacwh;
    246   char *usr;
    247   pacwh = GNUNET_new(struct TALER_BANK_PostCreateWithdrawalHandle);
    248 
    249   pacwh->ctx = ctx;
    250   pacwh->authorization = authorization;
    251 
    252   GNUNET_asprintf(&usr,
    253                   "accounts/%s/withdrawals",
    254                   username);
    255   pacwh->job_url = TALER_url_join(base_url,
    256                                  usr,
    257                                  NULL);
    258   pacwh->easy_handle = TALER_BANK_curl_easy_get_ (pacwh->job_url);
    259   if (NULL == pacwh->easy_handle)
    260   {
    261     GNUNET_free (usr);
    262     GNUNET_break (0);
    263     TALER_BANK_post_withdrawal_create_cancel (pacwh);
    264     return GNUNET_NO;
    265   }
    266   GNUNET_free (usr);
    267   return pacwh;
    268 }
    269 
    270 /**
    271  * Post accounts/withdrawal
    272  */
    273 enum GNUNET_GenericReturnValue
    274 TALER_BANK_post_accounts_withdrawal ( struct TALER_BANK_PostCreateWithdrawalHandle *handle,
    275                                       const struct TALER_BANK_AccountCreateWithdrawalRequest *req_ctx,
    276                                       TALER_BANK_CreateWithdrawalCallback pacw_cb,
    277                                       void *pacw_cb_cls)
    278 {
    279   json_t *req;
    280 
    281   handle->woc_cb = pacw_cb;
    282   handle->woc_cb_cls = pacw_cb_cls;
    283 
    284   req = GNUNET_JSON_PACK (
    285     GNUNET_JSON_pack_allow_null ((TALER_amount_is_valid(&req_ctx->amount) &
    286                                   !TALER_amount_is_zero(&req_ctx->amount)) ? 
    287                                 TALER_JSON_pack_amount ("amount",
    288                                                         &req_ctx->amount):
    289                                 GNUNET_JSON_pack_string ("amount", NULL)),
    290     GNUNET_JSON_pack_allow_null ((TALER_amount_is_valid(&req_ctx->suggested_amount) &
    291                                   !TALER_amount_is_zero(&req_ctx->suggested_amount)) ? 
    292                                 TALER_JSON_pack_amount ("suggested_amount",
    293                                                         &req_ctx->suggested_amount):
    294                                 GNUNET_JSON_pack_string ("suggested_amount", NULL)),
    295                                 GNUNET_JSON_pack_bool("no_amount_to_wallet",
    296                                                       req_ctx->no_amount_to_wallet));
    297 
    298   
    299   if(GNUNET_OK != DIGITIZER_setup_auth_(handle->easy_handle,handle->authorization))
    300   {
    301       GNUNET_break(0);
    302       TALER_BANK_post_withdrawal_create_cancel(handle);
    303       return GNUNET_NO;
    304   }
    305     if(GNUNET_OK !=
    306     TALER_curl_easy_post(&handle->post_ctx,
    307                          handle->easy_handle,
    308                          req))
    309   {
    310       GNUNET_break(0);
    311       TALER_BANK_post_withdrawal_create_cancel(handle);
    312       return GNUNET_NO;
    313   }
    314   
    315   json_decref(req);
    316   GNUNET_log( GNUNET_ERROR_TYPE_INFO,
    317               "Requesting URL `%s'.\n",
    318               handle->job_url);
    319   handle->job = GNUNET_CURL_job_add2(handle->ctx,
    320                                       handle->easy_handle,
    321                                       handle->post_ctx.headers,
    322                                       &response_cb,
    323                                       handle);
    324   if(NULL == handle->job)
    325   {
    326       GNUNET_break(0);
    327       TALER_BANK_post_withdrawal_create_cancel(handle);
    328       return GNUNET_NO;
    329   }
    330           
    331   return GNUNET_OK;
    332 }
    333 
    334 
    335 
    336 void
    337 TALER_BANK_post_withdrawal_create_cancel ( struct TALER_BANK_PostCreateWithdrawalHandle *pacwh)
    338 {
    339     if(NULL != pacwh->job)
    340     {
    341         GNUNET_CURL_job_cancel(pacwh->job);
    342         pacwh->job = NULL;
    343         pacwh->easy_handle = NULL;
    344     }
    345     if(NULL != pacwh->easy_handle)
    346     {
    347       curl_easy_cleanup (pacwh->easy_handle);
    348       pacwh->easy_handle = NULL;
    349     }
    350     TALER_curl_easy_post_finished(&pacwh->post_ctx);
    351     GNUNET_free(pacwh->job_url);
    352     GNUNET_free(pacwh);
    353 }