cash2ecash

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

commit 50793852584caf876ca53915828aa7e92032d70e
parent 87b1373d9a793ea45264f7c09a7a6abf0721896d
Author: Tellenbach Reto <tellr1@bfh.ch>
Date:   Sun,  7 Jun 2026 16:48:32 +0200

[new] Tapler-api: GET /accounts//withdrawal api implmentation done

Diffstat:
Msrc/lib/CMakeLists.txt | 3++-
Asrc/lib/bank_api_post_accounts_withdrawals.c | 353+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/bank_api_post_accounts_withdrawals.h | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/taler-digitizer.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
4 files changed, 606 insertions(+), 15 deletions(-)

diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt @@ -2,7 +2,8 @@ add_library(bank bank_api_get_config.c bank_api_curl_defaults.c - bank_api_get_accounts.c) + bank_api_get_accounts.c + bank_api_post_accounts_withdrawals.c) add_library(common api_common.c api_parse.c) diff --git a/src/lib/bank_api_post_accounts_withdrawals.c b/src/lib/bank_api_post_accounts_withdrawals.c @@ -0,0 +1,353 @@ +/* + This file is part of TALER cash2ecash + Copyright (C) 2026 GNUnet e.V. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ +/** + * @file lib/bank_api_post_accounts_withdrawals.c + * @brief implements the Taler Bank API "POST accounts/$USERNAME/withdrawals" handler + * @author Reto Tellenbach + */ + +#include <microhttpd.h> +#include "taler/taler_json_lib.h" +#include "bank_api_post_accounts_withdrawals.h" + +/** + * Log error related to CURL operations. + * + * @param type log level + * @param function which function failed to run + * @param code what was the curl error code + */ +#define CURL_STRERROR(type, function, code) \ + GNUNET_log (type, \ + "Curl function `%s' has failed at `%s:%d' with error: %s", \ + function, __FILE__, __LINE__, curl_easy_strerror (code)); + +/** + * Handle for the accounts create withdrawal request. + */ +struct TALER_BANK_PostCreateWithdrawalHandle +{ + /** + * The context of this handle + */ + struct GNUNET_CURL_Context *ctx; + + /** + * curle easy handle + */ + CURL *easy_handle; + + /** + * Context for curl easy post. Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Authentification date to access bank account + */ + const struct DIGITIZER_BankAuthenticationData *authorization; + + /** + * Function to call with the , + * NULL if this has already been done. + */ + TALER_BANK_CreateWithdrawalCallback woc_cb; + + /** + * Closure to pass to + * ater withdrawal operation creation + */ + void *woc_cb_cls; + + /** + * Data for the request to get the accounts/$USERNAME of a bank, + * NULL once we are past stage #MHS_INIT. + */ + struct GNUNET_CURL_Job *job; + + /** + * The whole request line + */ + char *job_url; + +}; + + +/** + * Decode the JSON in @a resp_obj from the accounts/$USERNAME/withdrawal response + * + * @param[in] resp_obj JSON object to parse + * @param[in,out] vi where to store the results we decoded + * @param[out] vc where to store account info data + * @return #TALER_EC_NONE on success + */ +static enum TALER_ErrorCode +decode_config_json (const json_t *resp_obj, + struct TALER_BANK_CreateWithdrawalInformatio *vi) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received body\n`%s'\n", + json_dumps(resp_obj, 0)); + + + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("withdrawal_id", + &vi->withdrawal_id), + GNUNET_JSON_spec_string ("taler_withdraw_uri", + &vi->taler_withdraw_uri), + GNUNET_JSON_spec_end () + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return TALER_EC_GENERIC_JSON_INVALID; + } + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return TALER_EC_GENERIC_JSON_INVALID; + } + + return TALER_EC_NONE; +} + + +/** + * Callback used when http reply arived to a /accounts/$USERNAME/withdrawal request. + * + * @param cls the `struct TALER_BANK_PostCreateWithdrawalHandle` + * @param response_code HTTP response code or 0 on error + * @param gresp_obj JSON result, NULL on error, must be a `const json_t *` + */ +static void +response_cb(void *cls, + long response_code, + const void *gresp_obj) +{ + struct TALER_BANK_PostCreateWithdrawalHandle *pcwh = cls; + const json_t *resp_obj = gresp_obj; + + struct TALER_BANK_CreateWithdrawalResponse pacwr = { + .hr.response = resp_obj, + .hr.http_status = (unsigned int)response_code + }; + + pcwh->job = NULL; //job was successfull, curl job cancel not needed anymore in cleanup + pcwh->easy_handle = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received from URL `%s' with status %ld.\n", + pcwh->job_url, + response_code); + + switch (response_code) + { + case 0: + GNUNET_break_op (0); + pacwr.hr.ec = TALER_EC_INVALID; + break; + case MHD_HTTP_OK: + if (NULL == resp_obj) + { + GNUNET_break_op (0); + pacwr.hr.http_status = 0; + pacwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + pacwr.hr.ec = decode_config_json (resp_obj, + &pacwr.details.ok.wopd); + if (TALER_EC_NONE != pacwr.hr.ec) + { + GNUNET_break_op (0); + pacwr.hr.http_status = 0; + break; + } + break; + case MHD_HTTP_UNAUTHORIZED: + pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj); + pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Invalid or missing credentials %u/%d\n", + (unsigned int) response_code, + (int) pacwr.hr.ec); + break; + case MHD_HTTP_FORBIDDEN: + pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj); + pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Missing rights %u/%d\n", + (unsigned int) response_code, + (int) pacwr.hr.ec); + break; + case MHD_HTTP_NOT_FOUND: + pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj); + pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "The account pointed by $USERNAME was not found %u/%d\n", + (unsigned int) response_code, + (int) pacwr.hr.ec); + break; + case MHD_HTTP_CONFLICT: + pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj); + pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "The account does not have sufficient funds %u/%d\n", + (unsigned int) response_code, + (int) pacwr.hr.ec); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj); + pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + break; + default: + pacwr.hr.ec = TALER_JSON_get_error_code (resp_obj); + pacwr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) pacwr.hr.ec); + break; + } + + pcwh->woc_cb (pcwh->woc_cb_cls, &pacwr); + TALER_BANK_post_withdrawal_create_cancel(pcwh); +} + +/** + * create handle for poost accounts/withdrawal/ request + */ +struct TALER_BANK_PostCreateWithdrawalHandle * +TALER_BANK_post_accounts_withdrawal_create ( struct GNUNET_CURL_Context *ctx, + const char *base_url, + const char *username, + const struct DIGITIZER_BankAuthenticationData *authorization) +{ + struct TALER_BANK_PostCreateWithdrawalHandle *pacwh; + char *usr; + pacwh = GNUNET_new(struct TALER_BANK_PostCreateWithdrawalHandle); + + pacwh->ctx = ctx; + pacwh->authorization = authorization; + + GNUNET_asprintf(&usr, + "accounts/%s/withdrawals", + username); + pacwh->job_url = TALER_url_join(base_url, + usr, + NULL); + pacwh->easy_handle = TALER_BANK_curl_easy_get_ (pacwh->job_url); + if (NULL == pacwh->easy_handle) + { + GNUNET_free (usr); + GNUNET_break (0); + TALER_BANK_post_withdrawal_create_cancel (pacwh); + return GNUNET_NO; + } + GNUNET_free (usr); + return pacwh; +} + +/** + * Post accounts/withdrawal + */ +enum GNUNET_GenericReturnValue +TALER_BANK_post_accounts_withdrawal ( struct TALER_BANK_PostCreateWithdrawalHandle *handle, + const struct TALER_BANK_AccountCreateWithdrawalRequest *req_ctx, + TALER_BANK_CreateWithdrawalCallback pacw_cb, + void *pacw_cb_cls) +{ + json_t *req; + + handle->woc_cb = pacw_cb; + handle->woc_cb_cls = pacw_cb_cls; + + req = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ((TALER_amount_is_valid(&req_ctx->amount) & + !TALER_amount_is_zero(&req_ctx->amount)) ? + TALER_JSON_pack_amount ("amount", + &req_ctx->amount): + GNUNET_JSON_pack_string ("amount", NULL)), + GNUNET_JSON_pack_allow_null ((TALER_amount_is_valid(&req_ctx->suggested_amount) & + !TALER_amount_is_zero(&req_ctx->suggested_amount)) ? + TALER_JSON_pack_amount ("suggested_amount", + &req_ctx->suggested_amount): + GNUNET_JSON_pack_string ("suggested_amount", NULL)), + GNUNET_JSON_pack_bool("no_amount_to_wallet", + req_ctx->no_amount_to_wallet)); + + + if(GNUNET_OK != DIGITIZER_setup_auth_(handle->easy_handle,handle->authorization)) + { + GNUNET_break(0); + TALER_BANK_post_withdrawal_create_cancel(handle); + return GNUNET_NO; + } + if(GNUNET_OK != + TALER_curl_easy_post(&handle->post_ctx, + handle->easy_handle, + req)) + { + GNUNET_break(0); + TALER_BANK_post_withdrawal_create_cancel(handle); + return GNUNET_NO; + } + + json_decref(req); + GNUNET_log( GNUNET_ERROR_TYPE_INFO, + "Requesting URL `%s'.\n", + handle->job_url); + handle->job = GNUNET_CURL_job_add2(handle->ctx, + handle->easy_handle, + handle->post_ctx.headers, + &response_cb, + handle); + if(NULL == handle->job) + { + GNUNET_break(0); + TALER_BANK_post_withdrawal_create_cancel(handle); + return GNUNET_NO; + } + + return GNUNET_OK; +} + + + +void +TALER_BANK_post_withdrawal_create_cancel ( struct TALER_BANK_PostCreateWithdrawalHandle *pacwh) +{ + if(NULL != pacwh->job) + { + GNUNET_CURL_job_cancel(pacwh->job); + pacwh->job = NULL; + pacwh->easy_handle = NULL; + } + if(NULL != pacwh->easy_handle) + { + curl_easy_cleanup (pacwh->easy_handle); + pacwh->easy_handle = NULL; + } + TALER_curl_easy_post_finished(&pacwh->post_ctx); + GNUNET_free(pacwh->job_url); + GNUNET_free(pacwh); +} diff --git a/src/lib/bank_api_post_accounts_withdrawals.h b/src/lib/bank_api_post_accounts_withdrawals.h @@ -0,0 +1,169 @@ +/* + This file is part of TALER cash2ecash + Copyright (C) 2026 GNUnet e.V. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ +/** + * @file lib/bank_api_post_accounts_withdrawals.h + * @brief implements the Taler Bank API "POST accounts/$USERNAME/withdrawals" handler + * @author Reto Tellenbach + */ +#ifndef BANK_API_POST_ACCOUNTS_WITHDRAWALS_H +#define BANK_API_POST_ACCOUNTS_WITHDRAWALS_H + +#include <taler/taler_curl_lib.h> +#include "bank_api_curl_defaults.h" +#include "api_common.h" +#include "taler/taler_bank_service.h" + + +/** + * @brief withdrawal operation information + * provided for request + */ +struct TALER_BANK_AccountCreateWithdrawalRequest +{ + /** + * Amount to withdraw. + * Optionaly and left out for Digitizer withdrawal operation creation + */ + struct TALER_Amount amount; + + /** + * Suggested amount to withdraw + * Optionaly and left out for Digitizer withdrawal operations + */ + struct TALER_Amount suggested_amount; + + /** + * If true, tell the wallet not to allow the user to + * specify an amount to withdraw and to not provide + * any amount when registering with the withdrawal operation. + * Set true for Digitizer withdrawals + */ + bool no_amount_to_wallet; +}; + +/** + * Withdrawal operation information from creation + */ +struct TALER_BANK_CreateWithdrawalInformatio +{ + /** + * ID identifying the operation being created + */ + const char *withdrawal_id; + + /** + * URI that can be passed to the wallet to initiate the withdrawal + */ + const char *taler_withdraw_uri; +}; + +/** + * Response details for a accounts/$USERNAME/withdrawals request + */ +struct TALER_BANK_CreateWithdrawalResponse +{ + + /** + * HTTP response + */ + struct TALER_BANK_HttpResponse hr; + + /** + * Details returned depending on the @e http_status. + */ + union + { + + /** + * Details if status was request was succesfull + */ + struct + { + + /** + * Withdrawal operation data returned by + * accounts/$USERNAME/withdrawal + */ + struct TALER_BANK_CreateWithdrawalInformatio wopd; + + } ok; + + } details; + +}; + + +/** + * Function called with information about the bank. + * + * @param cls closure + * @param vr response data + */ +typedef void +(*TALER_BANK_CreateWithdrawalCallback) ( + void *cls, + const struct TALER_BANK_CreateWithdrawalResponse *vr); + + +/** + * Handle for the post accounts create withdrawal request. + */ +struct TALER_BANK_PostCreateWithdrawalHandle; + + +/** + * create handle for post TALER_BANK_post_accounts_withdrawal() + * @param ctx curl context + * @param url bank url + * @param username bank account name + * @param authorization authentication for account + * @param accounts_cb callback + * @param accounts_cb_cls callback context + * @return NULL on failure + */ +struct TALER_BANK_PostCreateWithdrawalHandle * +TALER_BANK_post_accounts_withdrawal_create ( struct GNUNET_CURL_Context *ctx, + const char *base_url, + const char *username, + const struct DIGITIZER_BankAuthenticationData *authorization); + + +/** + * Http request POST /accounts/withdrawal + * @param handle handle, is created with TALER_BANK_post_accounts_withdrawal_create() + * @param req_ctx request body data + * @param pacw_cb callback + * @param pacw_cb_cls callback context + * @return GNUNET_NO on failure + */ +enum GNUNET_GenericReturnValue +TALER_BANK_post_accounts_withdrawal ( struct TALER_BANK_PostCreateWithdrawalHandle *handle, + const struct TALER_BANK_AccountCreateWithdrawalRequest *req_ctx, + TALER_BANK_CreateWithdrawalCallback pacw_cb, + void *pacw_cb_cls); + + +/** + * Cancel accounts create withdrawal request. Frees if neccessary + * @param account handle + */ +void +TALER_BANK_post_withdrawal_create_cancel ( struct TALER_BANK_PostCreateWithdrawalHandle *pacwh); + +#endif diff --git a/src/taler-digitizer.c b/src/taler-digitizer.c @@ -29,6 +29,7 @@ #include "taler/taler_digitizer_service.h" #include "lib/bank_api_get_config.h" #include "lib/bank_api_get_accounts.h" +#include "lib/bank_api_post_accounts_withdrawals.h" #include "digitizer_display.h" @@ -176,6 +177,10 @@ static struct TALER_BANK_GetConfigHandle *get_config_handle; */ static struct TALER_BANK_GetAccountsHandle *get_accounts_handle; +/** + * Handle for post_accounts_withdrawal request + */ +static struct TALER_BANK_PostCreateWithdrawalHandle *post_accounts_withdrawal_handle; enum DIGITIZER_states @@ -184,14 +189,22 @@ DIGITIZER_states * Init by loading configs */ DIGITIZER_STATE_INIT, + /** * */ DIGITIZER_STATE_IDLE, + + /** + * + */ + DIGITIZER_STATE_CREATE_QR, + /** * */ DIGITIZER_STATE_SCAN_QR, + /** * */ @@ -263,6 +276,12 @@ struct DIGITIZER_StateContext * in init state of each withdrawal process */ struct TALER_Amount withdrawal_balance; + + /** + * withdrawal operation information + * url and id + */ + struct TALER_BANK_CreateWithdrawalInformatio wi; }; static enum DIGITIZER_states state; @@ -378,7 +397,10 @@ on_get_config_done (void *cls, return; } - +/** + * callback of accounts request + * used in the Idle state + */ static void on_get_accounts_done(void *cls, const struct TALER_BANK_AccountsResponse *vr) @@ -389,7 +411,7 @@ on_get_accounts_done(void *cls, get_accounts_handle = NULL; struct TALER_Amount account_max_withdrawal; - if(GNUNET_OK != strcmp("active", + if(0 != strcmp("active", vr->details.ok.acc.status)) { GNUNET_log_config_invalid(GNUNET_ERROR_TYPE_ERROR, @@ -419,11 +441,30 @@ on_get_accounts_done(void *cls, - state = DIGITIZER_STATE_SCAN_QR; + state = DIGITIZER_STATE_CREATE_QR; GNUNET_SCHEDULER_add_now(state_controller_task,NULL); return; } +/** + * callback of accounts withdrawal request + * used in the QR Create state + */ +static void +on_post_accounts_withdrawal_done(void *cls, + const struct TALER_BANK_CreateWithdrawalResponse *vr) +{ + TALER_LOG_DEBUG ("Callback of accounts/$USERNAME/withdrawal\n"); + + (void) cls; + post_accounts_withdrawal_handle = NULL; + + state_ctx->wi = vr->details.ok.wopd; + + state = DIGITIZER_STATE_SCAN_QR; + GNUNET_SCHEDULER_add_now(state_controller_task,NULL); + return; +} /** * @brief Cleanup when no task is scheduled anymore @@ -442,6 +483,11 @@ shutdown_task (void *cls) TALER_BANK_get_config_cancel (get_config_handle); get_config_handle = NULL; } + if (NULL != post_accounts_withdrawal_handle) + { + TALER_BANK_post_withdrawal_create_cancel(post_accounts_withdrawal_handle); + post_accounts_withdrawal_handle = NULL; + } if (NULL != reschedule_ctx) { GNUNET_CURL_gnunet_rc_destroy (reschedule_ctx); @@ -878,20 +924,35 @@ static void Idle_state_task(void *cls) } /** - * Idle state - * prepare for new withdrawal process and - * check for start signal from UI + * Create QR state + * create QR code with a new WOPID + * check for cancle from UI */ -static void ScanQR_state_task(void *cls) +static void CreateQR_state_task(void *cls) { (void)cls; + struct TALER_BANK_AccountCreateWithdrawalRequest *acwr; + acwr = GNUNET_new(struct TALER_BANK_AccountCreateWithdrawalRequest); + acwr->no_amount_to_wallet = true; - get_accounts_handle = TALER_BANK_get_accounts (curl_ctx, - cfg_bank_base_url, - cfg_bank_account_username, - cfg_bank_authentication, - &on_get_accounts_done, - NULL); + post_accounts_withdrawal_handle = + TALER_BANK_post_accounts_withdrawal_create (curl_ctx, + cfg_bank_base_url, + cfg_bank_account_username, + cfg_bank_authentication); + if(GNUNET_OK != + TALER_BANK_post_accounts_withdrawal(post_accounts_withdrawal_handle, + acwr, + on_post_accounts_withdrawal_done, + NULL)) + { + TALER_LOG_ERROR("TALER_BANK_post_accounts_withdrawal\n"); + state = DIGITIZER_STATE_CANCEL_WITHDRAWAL; + // give context for non terminal errors! + GNUNET_SCHEDULER_add_now(state_controller_task,NULL); + } + GNUNET_free(acwr); + return; } /** @@ -918,12 +979,19 @@ static void state_controller_task(void *cls) GNUNET_SCHEDULER_add_now(TerminalError_state_task,NULL); return; } + case DIGITIZER_STATE_CREATE_QR: + { + GNUNET_SCHEDULER_add_now(CreateQR_state_task,NULL); + return; + } + /* case DIGITIZER_STATE_SCAN_QR: { GNUNET_SCHEDULER_add_now(ScanQR_state_task,NULL); return; } - + */ + default: { TALER_LOG_ERROR("Gost-State in state machine\n");