cash2ecash

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

bank_api_post_accounts_withdrawals_confirm.c (10837B)


      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_confirm.c
     21  * @brief implements the Taler Bank API "POST /accounts/$USERNAME/withdrawals/$WITHDRAWAL_ID/confirm" 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_confirm.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_PostWithdrawalConfirmHandle
     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_WithdrawalConfirmCallback 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/confirm response when challenged
     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 challenge information
     99  * @return #TALER_EC_NONE on success
    100  */
    101 static enum TALER_ErrorCode
    102 decode_challenge_json (const json_t *resp_obj,
    103                     struct TALER_BANK_ChallengeResponse *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_json ("balance",
    112                             &vi->blc),
    113     GNUNET_JSON_spec_end ()
    114   };
    115 
    116   if (JSON_OBJECT != json_typeof (resp_obj))
    117   {
    118     GNUNET_break_op (0);
    119     return TALER_EC_GENERIC_JSON_INVALID;
    120   }
    121   if (GNUNET_OK !=
    122       GNUNET_JSON_parse (resp_obj,
    123                          spec,
    124                          NULL, NULL))
    125   {
    126     GNUNET_break_op (0);
    127     return TALER_EC_GENERIC_JSON_INVALID;
    128   }
    129 
    130   return TALER_EC_NONE;
    131 }
    132 
    133 
    134 /**
    135  * Callback used when http reply arived to a /accounts/$USERNAME/withdrawal/confirm request.
    136  *
    137  * @param cls the `struct TALER_BANK_PostWithdrawalConfirmHandle`
    138  * @param response_code HTTP response code or 0 on error
    139  * @param gresp_obj JSON result, NULL on error, must be a `const json_t *`
    140  */
    141 static void
    142 response_cb(void *cls,
    143             long response_code,
    144             const void *gresp_obj)
    145 {
    146     struct TALER_BANK_PostWithdrawalConfirmHandle *pcwh = cls;
    147     const json_t *resp_obj = gresp_obj;
    148     
    149     struct TALER_BANK_WithdrawalConfirmResponse pacwr = {
    150         .hr.response = resp_obj,
    151         .hr.http_status = (unsigned int)response_code
    152     };
    153     
    154     pcwh->job = NULL; //job was successfull, curl job cancel not needed anymore in cleanup
    155     pcwh->easy_handle = NULL;
    156     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    157               "Received from URL `%s' with status %ld.\n",
    158               pcwh->job_url,
    159               response_code);
    160     
    161     switch (response_code)
    162   {
    163   case 0:
    164     GNUNET_break_op (0);
    165     pacwr.hr.ec = TALER_EC_INVALID;
    166     break;
    167   case MHD_HTTP_ACCEPTED:
    168     if (NULL == resp_obj)
    169     {
    170       GNUNET_break_op (0);
    171       pacwr.hr.http_status = 0;
    172       break;
    173     }
    174     pacwr.hr.ec = decode_challenge_json (resp_obj,
    175                                         &pacwr.details.ok.challenge);
    176     if (TALER_EC_NONE != pacwr.hr.ec)
    177     {
    178       GNUNET_break_op (0);
    179       pacwr.hr.http_status = 0;
    180       break;
    181     }
    182     break;
    183   case MHD_HTTP_OK:
    184     if (NULL == resp_obj)
    185     {
    186       GNUNET_break_op (0);
    187       pacwr.hr.http_status = 0;
    188       pacwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    189       break;
    190     }
    191     if (TALER_EC_NONE != pacwr.hr.ec)
    192     {
    193       GNUNET_break_op (0);
    194       pacwr.hr.http_status = 0;
    195       break;
    196     }
    197     break;
    198   case MHD_HTTP_UNAUTHORIZED:
    199     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    200     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    201     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    202                 "Invalid or missing credentials %u/%d\n",
    203                 (unsigned int) response_code,
    204                 (int) pacwr.hr.ec);
    205     break;
    206   case MHD_HTTP_FORBIDDEN:
    207     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    208     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    209     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    210                 "Missing rights %u/%d\n",
    211                 (unsigned int) response_code,
    212                 (int) pacwr.hr.ec);
    213     break;
    214   case MHD_HTTP_NOT_FOUND:
    215     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    216     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    217     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    218                 "The operation was not found. %u/%d\n",
    219                 (unsigned int) response_code,
    220                 (int) pacwr.hr.ec);
    221     break;
    222   case MHD_HTTP_CONFLICT:
    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_INFO,
    226                 "Conflict %u/%d\n",
    227                 (unsigned int) response_code,
    228                 (int) pacwr.hr.ec);
    229     break;
    230   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    231     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    232     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    233     break;
    234   default:
    235     pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj);
    236     pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    237     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    238                 "Unexpected response code %u/%d\n",
    239                 (unsigned int) response_code,
    240                 (int) pacwr.hr.ec);
    241     break;
    242   }
    243 
    244   pcwh->woc_cb (pcwh->woc_cb_cls, &pacwr);
    245   TALER_BANK_post_withdrawal_confirm_cancel(pcwh);
    246 }
    247 
    248 /**
    249  * create handle for poost accounts/withdrawal/confirm request
    250  */
    251 struct TALER_BANK_PostWithdrawalConfirmHandle *
    252 TALER_BANK_post_withdrawal_confirm_create ( struct GNUNET_CURL_Context *ctx,
    253                                             const char *base_url,
    254                                             const char *username,
    255                                             const char *wopid,
    256                                             const struct DIGITIZER_BankAuthenticationData *authorization)
    257 {
    258   struct TALER_BANK_PostWithdrawalConfirmHandle *pwc;
    259   char *usr;
    260   pwc = GNUNET_new(struct TALER_BANK_PostWithdrawalConfirmHandle);
    261 
    262   pwc->ctx = ctx;
    263   pwc->authorization = authorization;
    264 
    265   GNUNET_asprintf(&usr,
    266                   "accounts/%s/withdrawals/%s/confirm",
    267                   username,
    268                   wopid);
    269   pwc->job_url = TALER_url_join(base_url,
    270                                  usr,
    271                                  NULL);
    272   pwc->easy_handle = TALER_BANK_curl_easy_get_ (pwc->job_url);
    273   if (NULL == pwc->easy_handle)
    274   {
    275     GNUNET_free (usr);
    276     GNUNET_break (0);
    277     TALER_BANK_post_withdrawal_confirm_cancel (pwc);
    278     return GNUNET_NO;
    279   }
    280   GNUNET_free (usr);
    281   return pwc;
    282 }
    283 
    284 /**
    285  * Post accounts/withdrawal
    286  */
    287 enum GNUNET_GenericReturnValue
    288 TALER_BANK_post_withdrawal_confirm ( struct TALER_BANK_PostWithdrawalConfirmHandle *handle,
    289                                       const struct TALER_BANK_AccountWithdrawalConfirmRequest*req_ctx,
    290                                       TALER_BANK_WithdrawalConfirmCallback pwc_cb,
    291                                       void *pwc_cb_cls)
    292 {
    293   json_t *req;
    294 
    295   handle->woc_cb = pwc_cb;
    296   handle->woc_cb_cls = pwc_cb_cls;
    297 
    298   req = GNUNET_JSON_PACK (
    299     GNUNET_JSON_pack_allow_null ((TALER_amount_is_valid(&req_ctx->amount) &
    300                                   !TALER_amount_is_zero(&req_ctx->amount)) ? 
    301                                 TALER_JSON_pack_amount ("amount",
    302                                                         &req_ctx->amount):
    303                                 GNUNET_JSON_pack_string ("amount", NULL)));
    304 
    305   
    306   if(GNUNET_OK != DIGITIZER_setup_auth_(handle->easy_handle,handle->authorization))
    307   {
    308       GNUNET_break(0);
    309       TALER_BANK_post_withdrawal_confirm_cancel(handle);
    310       return GNUNET_NO;
    311   }
    312     if(GNUNET_OK !=
    313     TALER_curl_easy_post(&handle->post_ctx,
    314                          handle->easy_handle,
    315                          req))
    316   {
    317       GNUNET_break(0);
    318       TALER_BANK_post_withdrawal_confirm_cancel(handle);
    319       return GNUNET_NO;
    320   }
    321   
    322   json_decref(req);
    323   GNUNET_log( GNUNET_ERROR_TYPE_INFO,
    324               "Requesting URL `%s'.\n",
    325               handle->job_url);
    326   handle->job = GNUNET_CURL_job_add2(handle->ctx,
    327                                       handle->easy_handle,
    328                                       handle->post_ctx.headers,
    329                                       &response_cb,
    330                                       handle);
    331   if(NULL == handle->job)
    332   {
    333       GNUNET_break(0);
    334       TALER_BANK_post_withdrawal_confirm_cancel(handle);
    335       return GNUNET_NO;
    336   }
    337           
    338   return GNUNET_OK;
    339 }
    340 
    341 
    342 
    343 void
    344 TALER_BANK_post_withdrawal_confirm_cancel ( struct TALER_BANK_PostWithdrawalConfirmHandle *pwch)
    345 {
    346     if(NULL != pwch->job)
    347     {
    348         GNUNET_CURL_job_cancel(pwch->job);
    349         pwch->job = NULL;
    350         pwch->easy_handle = NULL;
    351     }
    352     if(NULL != pwch->easy_handle)
    353     {
    354       curl_easy_cleanup (pwch->easy_handle);
    355       pwch->easy_handle = NULL;
    356     }
    357     TALER_curl_easy_post_finished(&pwch->post_ctx);
    358     GNUNET_free(pwch->job_url);
    359     GNUNET_free(pwch);
    360 }