bank_api_debit.c (11323B)
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 bool no_debit_fee; 113 struct GNUNET_JSON_Specification hist_spec[] = { 114 TALER_JSON_spec_amount_any ("amount", 115 &td->amount), 116 GNUNET_JSON_spec_mark_optional ( 117 TALER_JSON_spec_amount_any ("debit_fee", 118 &td->debit_fee), 119 &no_debit_fee), 120 GNUNET_JSON_spec_timestamp ("date", 121 &td->execution_date), 122 GNUNET_JSON_spec_uint64 ("row_id", 123 &td->serial_id), 124 GNUNET_JSON_spec_fixed_auto ("wtid", 125 &td->wtid), 126 TALER_JSON_spec_full_payto_uri ("credit_account", 127 &td->credit_account_uri), 128 TALER_JSON_spec_web_url ("exchange_base_url", 129 &td->exchange_base_url), 130 GNUNET_JSON_spec_mark_optional ( 131 GNUNET_JSON_spec_string ("metadata", 132 &td->metadata), 133 NULL), 134 GNUNET_JSON_spec_end () 135 }; 136 json_t *transaction = json_array_get (history_array, 137 i); 138 139 if (GNUNET_OK != 140 GNUNET_JSON_parse (transaction, 141 hist_spec, 142 NULL, 143 NULL)) 144 { 145 GNUNET_break_op (0); 146 GNUNET_free (dd); 147 return GNUNET_SYSERR; 148 } 149 if (no_debit_fee) 150 { 151 GNUNET_assert (GNUNET_OK == 152 TALER_amount_set_zero (td->amount.currency, 153 &td->debit_fee)); 154 } 155 else if (GNUNET_YES != 156 TALER_amount_cmp_currency (&td->amount, 157 &td->debit_fee)) 158 { 159 GNUNET_break_op (0); 160 GNUNET_free (dd); 161 return GNUNET_SYSERR; 162 } 163 } 164 dhr.details.ok.details_length = len; 165 dhr.details.ok.details = dd; 166 hh->hcb (hh->hcb_cls, 167 &dhr); 168 GNUNET_free (dd); 169 } 170 return GNUNET_OK; 171 } 172 173 174 /** 175 * Function called when we're done processing the 176 * HTTP /history/outgoing request. 177 * 178 * @param cls the `struct TALER_BANK_DebitHistoryHandle` 179 * @param response_code HTTP response code, 0 on error 180 * @param response parsed JSON result, NULL on error 181 */ 182 static void 183 handle_debit_history_finished (void *cls, 184 long response_code, 185 const void *response) 186 { 187 struct TALER_BANK_DebitHistoryHandle *hh = cls; 188 struct TALER_BANK_DebitHistoryResponse dhr = { 189 .http_status = response_code, 190 .response = response 191 }; 192 193 hh->job = NULL; 194 switch (response_code) 195 { 196 case 0: 197 dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 198 break; 199 case MHD_HTTP_OK: 200 if (GNUNET_OK != 201 parse_account_history (hh, 202 dhr.response)) 203 { 204 GNUNET_break_op (0); 205 dhr.http_status = 0; 206 dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 207 break; 208 } 209 TALER_BANK_debit_history_cancel (hh); 210 return; 211 case MHD_HTTP_NO_CONTENT: 212 break; 213 case MHD_HTTP_BAD_REQUEST: 214 /* This should never happen, either us or the bank is buggy 215 (or API version conflict); just pass JSON reply to the application */ 216 GNUNET_break_op (0); 217 dhr.ec = TALER_JSON_get_error_code (dhr.response); 218 break; 219 case MHD_HTTP_UNAUTHORIZED: 220 /* Nothing really to verify, bank says the HTTP Authentication 221 failed. May happen if HTTP authentication is used and the 222 user supplied a wrong username/password combination. */ 223 dhr.ec = TALER_JSON_get_error_code (dhr.response); 224 break; 225 case MHD_HTTP_NOT_FOUND: 226 /* Nothing really to verify: the bank is either unaware 227 of the endpoint (not a bank), or of the account. 228 We should pass the JSON (?) reply to the application */ 229 dhr.ec = TALER_JSON_get_error_code (dhr.response); 230 break; 231 case MHD_HTTP_INTERNAL_SERVER_ERROR: 232 /* Server had an internal issue; we should retry, but this API 233 leaves this to the application */ 234 dhr.ec = TALER_JSON_get_error_code (dhr.response); 235 break; 236 default: 237 /* unexpected response code */ 238 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 239 "Unexpected response code %u\n", 240 (unsigned int) response_code); 241 dhr.ec = TALER_JSON_get_error_code (dhr.response); 242 break; 243 } 244 hh->hcb (hh->hcb_cls, 245 &dhr); 246 TALER_BANK_debit_history_cancel (hh); 247 } 248 249 250 struct TALER_BANK_DebitHistoryHandle * 251 TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx, 252 const struct TALER_BANK_AuthenticationData *auth, 253 uint64_t start_row, 254 int64_t num_results, 255 struct GNUNET_TIME_Relative timeout, 256 TALER_BANK_DebitHistoryCallback hres_cb, 257 void *hres_cb_cls) 258 { 259 char url[128]; 260 struct TALER_BANK_DebitHistoryHandle *hh; 261 CURL *eh; 262 unsigned long long tms; 263 264 if (0 == num_results) 265 { 266 GNUNET_break (0); 267 return NULL; 268 } 269 270 tms = (unsigned long long) (timeout.rel_value_us 271 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 272 if ( ( (UINT64_MAX == start_row) && 273 (0 > num_results) ) || 274 ( (0 == start_row) && 275 (0 < num_results) ) ) 276 { 277 if ( (0 < num_results) && 278 (! GNUNET_TIME_relative_is_zero (timeout)) ) 279 GNUNET_snprintf (url, 280 sizeof (url), 281 "history/outgoing?limit=%lld&timeout_ms=%llu", 282 (long long) num_results, 283 tms); 284 else 285 GNUNET_snprintf (url, 286 sizeof (url), 287 "history/outgoing?limit=%lld", 288 (long long) num_results); 289 } 290 else 291 { 292 if ( (0 < num_results) && 293 (! GNUNET_TIME_relative_is_zero (timeout)) ) 294 GNUNET_snprintf (url, 295 sizeof (url), 296 "history/outgoing?limit=%lld&offset=%llu&timeout_ms=%llu", 297 (long long) num_results, 298 (unsigned long long) start_row, 299 tms); 300 else 301 GNUNET_snprintf (url, 302 sizeof (url), 303 "history/outgoing?limit=%lld&offset=%llu", 304 (long long) num_results, 305 (unsigned long long) start_row); 306 } 307 hh = GNUNET_new (struct TALER_BANK_DebitHistoryHandle); 308 hh->hcb = hres_cb; 309 hh->hcb_cls = hres_cb_cls; 310 hh->request_url = TALER_url_join (auth->wire_gateway_url, 311 url, 312 NULL); 313 if (NULL == hh->request_url) 314 { 315 GNUNET_free (hh); 316 GNUNET_break (0); 317 return NULL; 318 } 319 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 320 "Requesting debit history at `%s'\n", 321 hh->request_url); 322 eh = curl_easy_init (); 323 if ( (NULL == eh) || 324 (GNUNET_OK != 325 TALER_BANK_setup_auth_ (eh, 326 auth)) || 327 (CURLE_OK != 328 curl_easy_setopt (eh, 329 CURLOPT_URL, 330 hh->request_url)) ) 331 { 332 GNUNET_break (0); 333 TALER_BANK_debit_history_cancel (hh); 334 if (NULL != eh) 335 curl_easy_cleanup (eh); 336 return NULL; 337 } 338 if (0 != tms) 339 { 340 GNUNET_break (CURLE_OK == 341 curl_easy_setopt (eh, 342 CURLOPT_TIMEOUT_MS, 343 (long) tms + GRACE_PERIOD_MS)); 344 } 345 hh->job = GNUNET_CURL_job_add2 (ctx, 346 eh, 347 NULL, 348 &handle_debit_history_finished, 349 hh); 350 if (NULL == hh->job) 351 { 352 GNUNET_break (0); 353 /* GNUNET_CURL_job_add2() already released @a eh */ 354 TALER_BANK_debit_history_cancel (hh); 355 return NULL; 356 } 357 return hh; 358 } 359 360 361 void 362 TALER_BANK_debit_history_cancel (struct TALER_BANK_DebitHistoryHandle *hh) 363 { 364 if (NULL != hh->job) 365 { 366 GNUNET_CURL_job_cancel (hh->job); 367 hh->job = NULL; 368 } 369 GNUNET_free (hh->request_url); 370 GNUNET_free (hh); 371 } 372 373 374 /* end of bank_api_debit.c */