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