bank_api_debit.c (10354B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2017--2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License 7 as published by the Free Software Foundation; either version 3, 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 General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file bank-lib/bank_api_debit.c 21 * @brief Implementation of the /history/outgoing 22 * requests of the bank's HTTP API. 23 * @author Christian Grothoff 24 * @author Marcello Stanisci 25 */ 26 #include "bank_api_common.h" 27 #include <microhttpd.h> /* just for HTTP status codes */ 28 #include "taler/taler_signatures.h" 29 30 31 /** 32 * How much longer than the application-specified timeout 33 * do we wait (giving the server a chance to respond)? 34 */ 35 #define GRACE_PERIOD_MS 1000 36 37 38 /** 39 * @brief A /history/outgoing Handle 40 */ 41 struct TALER_BANK_DebitHistoryHandle 42 { 43 44 /** 45 * The url for this request. 46 */ 47 char *request_url; 48 49 /** 50 * Handle for the request. 51 */ 52 struct GNUNET_CURL_Job *job; 53 54 /** 55 * Function to call with the result. 56 */ 57 TALER_BANK_DebitHistoryCallback hcb; 58 59 /** 60 * Closure for @a cb. 61 */ 62 void *hcb_cls; 63 }; 64 65 66 /** 67 * Parse history given in JSON format and invoke the callback on each item. 68 * 69 * @param hh handle to the account history request 70 * @param history JSON array with the history 71 * @return #GNUNET_OK if history was valid and @a rhistory and @a balance 72 * were set, 73 * #GNUNET_SYSERR if there was a protocol violation in @a history 74 */ 75 static enum GNUNET_GenericReturnValue 76 parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh, 77 const json_t *history) 78 { 79 struct TALER_BANK_DebitHistoryResponse dhr = { 80 .http_status = MHD_HTTP_OK, 81 .ec = TALER_EC_NONE, 82 .response = history 83 }; 84 const json_t *history_array; 85 struct GNUNET_JSON_Specification spec[] = { 86 GNUNET_JSON_spec_array_const ("outgoing_transactions", 87 &history_array), 88 TALER_JSON_spec_full_payto_uri ("debit_account", 89 &dhr.details.ok.debit_account_uri), 90 GNUNET_JSON_spec_end () 91 }; 92 93 if (GNUNET_OK != 94 GNUNET_JSON_parse (history, 95 spec, 96 NULL, 97 NULL)) 98 { 99 GNUNET_break_op (0); 100 return GNUNET_SYSERR; 101 } 102 { 103 size_t len = json_array_size (history_array); 104 struct TALER_BANK_DebitDetails *dd; 105 106 GNUNET_break_op (0 != len); 107 dd = GNUNET_new_array (GNUNET_NZL (len), 108 struct TALER_BANK_DebitDetails); 109 for (unsigned int i = 0; i<len; i++) 110 { 111 struct TALER_BANK_DebitDetails *td = &dd[i]; 112 struct GNUNET_JSON_Specification hist_spec[] = { 113 TALER_JSON_spec_amount_any ("amount", 114 &td->amount), 115 GNUNET_JSON_spec_timestamp ("date", 116 &td->execution_date), 117 GNUNET_JSON_spec_uint64 ("row_id", 118 &td->serial_id), 119 GNUNET_JSON_spec_fixed_auto ("wtid", 120 &td->wtid), 121 TALER_JSON_spec_full_payto_uri ("credit_account", 122 &td->credit_account_uri), 123 TALER_JSON_spec_web_url ("exchange_base_url", 124 &td->exchange_base_url), 125 GNUNET_JSON_spec_end () 126 }; 127 json_t *transaction = json_array_get (history_array, 128 i); 129 130 if (GNUNET_OK != 131 GNUNET_JSON_parse (transaction, 132 hist_spec, 133 NULL, 134 NULL)) 135 { 136 GNUNET_break_op (0); 137 GNUNET_free (dd); 138 return GNUNET_SYSERR; 139 } 140 } 141 dhr.details.ok.details_length = len; 142 dhr.details.ok.details = dd; 143 hh->hcb (hh->hcb_cls, 144 &dhr); 145 GNUNET_free (dd); 146 } 147 return GNUNET_OK; 148 } 149 150 151 /** 152 * Function called when we're done processing the 153 * HTTP /history/outgoing request. 154 * 155 * @param cls the `struct TALER_BANK_DebitHistoryHandle` 156 * @param response_code HTTP response code, 0 on error 157 * @param response parsed JSON result, NULL on error 158 */ 159 static void 160 handle_debit_history_finished (void *cls, 161 long response_code, 162 const void *response) 163 { 164 struct TALER_BANK_DebitHistoryHandle *hh = cls; 165 struct TALER_BANK_DebitHistoryResponse dhr = { 166 .http_status = response_code, 167 .response = response 168 }; 169 170 hh->job = NULL; 171 switch (response_code) 172 { 173 case 0: 174 dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 175 break; 176 case MHD_HTTP_OK: 177 if (GNUNET_OK != 178 parse_account_history (hh, 179 dhr.response)) 180 { 181 GNUNET_break_op (0); 182 dhr.http_status = 0; 183 dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 184 break; 185 } 186 TALER_BANK_debit_history_cancel (hh); 187 return; 188 case MHD_HTTP_NO_CONTENT: 189 break; 190 case MHD_HTTP_BAD_REQUEST: 191 /* This should never happen, either us or the bank is buggy 192 (or API version conflict); just pass JSON reply to the application */ 193 GNUNET_break_op (0); 194 dhr.ec = TALER_JSON_get_error_code (dhr.response); 195 break; 196 case MHD_HTTP_UNAUTHORIZED: 197 /* Nothing really to verify, bank says the HTTP Authentication 198 failed. May happen if HTTP authentication is used and the 199 user supplied a wrong username/password combination. */ 200 dhr.ec = TALER_JSON_get_error_code (dhr.response); 201 break; 202 case MHD_HTTP_NOT_FOUND: 203 /* Nothing really to verify: the bank is either unaware 204 of the endpoint (not a bank), or of the account. 205 We should pass the JSON (?) reply to the application */ 206 dhr.ec = TALER_JSON_get_error_code (dhr.response); 207 break; 208 case MHD_HTTP_INTERNAL_SERVER_ERROR: 209 /* Server had an internal issue; we should retry, but this API 210 leaves this to the application */ 211 dhr.ec = TALER_JSON_get_error_code (dhr.response); 212 break; 213 default: 214 /* unexpected response code */ 215 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 216 "Unexpected response code %u\n", 217 (unsigned int) response_code); 218 dhr.ec = TALER_JSON_get_error_code (dhr.response); 219 break; 220 } 221 hh->hcb (hh->hcb_cls, 222 &dhr); 223 TALER_BANK_debit_history_cancel (hh); 224 } 225 226 227 struct TALER_BANK_DebitHistoryHandle * 228 TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx, 229 const struct TALER_BANK_AuthenticationData *auth, 230 uint64_t start_row, 231 int64_t num_results, 232 struct GNUNET_TIME_Relative timeout, 233 TALER_BANK_DebitHistoryCallback hres_cb, 234 void *hres_cb_cls) 235 { 236 char url[128]; 237 struct TALER_BANK_DebitHistoryHandle *hh; 238 CURL *eh; 239 unsigned long long tms; 240 241 if (0 == num_results) 242 { 243 GNUNET_break (0); 244 return NULL; 245 } 246 247 tms = (unsigned long long) (timeout.rel_value_us 248 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 249 if ( ( (UINT64_MAX == start_row) && 250 (0 > num_results) ) || 251 ( (0 == start_row) && 252 (0 < num_results) ) ) 253 { 254 if ( (0 < num_results) && 255 (! GNUNET_TIME_relative_is_zero (timeout)) ) 256 GNUNET_snprintf (url, 257 sizeof (url), 258 "history/outgoing?limit=%lld&long_poll_ms=%llu", 259 (long long) num_results, 260 tms); 261 else 262 GNUNET_snprintf (url, 263 sizeof (url), 264 "history/outgoing?limit=%lld", 265 (long long) num_results); 266 } 267 else 268 { 269 if ( (0 < num_results) && 270 (! GNUNET_TIME_relative_is_zero (timeout)) ) 271 GNUNET_snprintf (url, 272 sizeof (url), 273 "history/outgoing?limit=%lld&offset=%llu&long_poll_ms=%llu", 274 (long long) num_results, 275 (unsigned long long) start_row, 276 tms); 277 else 278 GNUNET_snprintf (url, 279 sizeof (url), 280 "history/outgoing?limit=%lld&offset=%llu", 281 (long long) num_results, 282 (unsigned long long) start_row); 283 } 284 hh = GNUNET_new (struct TALER_BANK_DebitHistoryHandle); 285 hh->hcb = hres_cb; 286 hh->hcb_cls = hres_cb_cls; 287 hh->request_url = TALER_url_join (auth->wire_gateway_url, 288 url, 289 NULL); 290 if (NULL == hh->request_url) 291 { 292 GNUNET_free (hh); 293 GNUNET_break (0); 294 return NULL; 295 } 296 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 297 "Requesting debit history at `%s'\n", 298 hh->request_url); 299 eh = curl_easy_init (); 300 if ( (NULL == eh) || 301 (GNUNET_OK != 302 TALER_BANK_setup_auth_ (eh, 303 auth)) || 304 (CURLE_OK != 305 curl_easy_setopt (eh, 306 CURLOPT_URL, 307 hh->request_url)) ) 308 { 309 GNUNET_break (0); 310 TALER_BANK_debit_history_cancel (hh); 311 if (NULL != eh) 312 curl_easy_cleanup (eh); 313 return NULL; 314 } 315 if (0 != tms) 316 { 317 GNUNET_break (CURLE_OK == 318 curl_easy_setopt (eh, 319 CURLOPT_TIMEOUT_MS, 320 (long) tms + GRACE_PERIOD_MS)); 321 } 322 hh->job = GNUNET_CURL_job_add2 (ctx, 323 eh, 324 NULL, 325 &handle_debit_history_finished, 326 hh); 327 return hh; 328 } 329 330 331 void 332 TALER_BANK_debit_history_cancel (struct TALER_BANK_DebitHistoryHandle *hh) 333 { 334 if (NULL != hh->job) 335 { 336 GNUNET_CURL_job_cancel (hh->job); 337 hh->job = NULL; 338 } 339 GNUNET_free (hh->request_url); 340 GNUNET_free (hh); 341 } 342 343 344 /* end of bank_api_debit.c */