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 }