merchant_api_merchant_get_order.c (15886B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2018, 2019, 2020 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file merchant_api_merchant_get_order.c 19 * @brief Implementation of the GET /private/orders/$ORDER request 20 * @author Christian Grothoff 21 * @author Marcello Stanisci 22 * @author Florian Dold 23 */ 24 #include "platform.h" 25 #include <curl/curl.h> 26 #include <jansson.h> 27 #include <microhttpd.h> /* just for HTTP status codes */ 28 #include <gnunet/gnunet_util_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include "taler_merchant_service.h" 31 #include "merchant_api_curl_defaults.h" 32 #include <taler/taler_json_lib.h> 33 #include <taler/taler_signatures.h> 34 35 36 /** 37 * Maximum number of refund details we return. 38 */ 39 #define MAX_REFUND_DETAILS 1024 40 41 /** 42 * Maximum number of wire details we return. 43 */ 44 #define MAX_WIRE_DETAILS 1024 45 46 47 /** 48 * @brief A GET /private/orders/$ORDER handle 49 */ 50 struct TALER_MERCHANT_OrderMerchantGetHandle 51 { 52 53 /** 54 * The url for this request. 55 */ 56 char *url; 57 58 /** 59 * Handle for the request. 60 */ 61 struct GNUNET_CURL_Job *job; 62 63 /** 64 * Function to call with the result. 65 */ 66 TALER_MERCHANT_OrderMerchantGetCallback cb; 67 68 /** 69 * Closure for @a cb. 70 */ 71 void *cb_cls; 72 73 /** 74 * Reference to the execution context. 75 */ 76 struct GNUNET_CURL_Context *ctx; 77 }; 78 79 80 /** 81 * Function called when we're done processing the GET /private/orders/$ORDER 82 * request and we got an HTTP status of OK and the order was unpaid. Parse 83 * the response and call the callback. 84 * 85 * @param omgh handle for the request 86 * @param[in,out] osr HTTP response we got 87 */ 88 static void 89 handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, 90 struct TALER_MERCHANT_OrderStatusResponse *osr) 91 { 92 struct GNUNET_JSON_Specification spec[] = { 93 TALER_JSON_spec_amount_any ( 94 "total_amount", 95 &osr->details.ok.details.unpaid.contract_amount), 96 GNUNET_JSON_spec_mark_optional ( 97 GNUNET_JSON_spec_string ( 98 "already_paid_order_id", 99 &osr->details.ok.details.unpaid.already_paid_order_id), 100 NULL), 101 GNUNET_JSON_spec_string ( 102 "taler_pay_uri", 103 &osr->details.ok.details.unpaid.taler_pay_uri), 104 GNUNET_JSON_spec_string ( 105 "summary", 106 &osr->details.ok.details.unpaid.summary), 107 GNUNET_JSON_spec_timestamp ( 108 "creation_time", 109 &osr->details.ok.details.unpaid.creation_time), 110 GNUNET_JSON_spec_end () 111 }; 112 113 if (GNUNET_OK != 114 GNUNET_JSON_parse (osr->hr.reply, 115 spec, 116 NULL, NULL)) 117 { 118 GNUNET_break_op (0); 119 osr->hr.http_status = 0; 120 osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 121 omgh->cb (omgh->cb_cls, 122 osr); 123 return; 124 } 125 osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID; 126 omgh->cb (omgh->cb_cls, 127 osr); 128 } 129 130 131 /** 132 * Function called when we're done processing the GET /private/orders/$ORDER 133 * request and we got an HTTP status of OK and the order was claimed but not 134 * paid. Parse the response and call the callback. 135 * 136 * @param omgh handle for the request 137 * @param[in,out] osr HTTP response we got 138 */ 139 static void 140 handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, 141 struct TALER_MERCHANT_OrderStatusResponse *osr) 142 { 143 struct GNUNET_JSON_Specification spec[] = { 144 GNUNET_JSON_spec_object_const ( 145 "contract_terms", 146 &osr->details.ok.details.claimed.contract_terms), 147 GNUNET_JSON_spec_end () 148 }; 149 150 if (GNUNET_OK != 151 GNUNET_JSON_parse (osr->hr.reply, 152 spec, 153 NULL, NULL)) 154 { 155 GNUNET_break_op (0); 156 osr->hr.http_status = 0; 157 osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 158 omgh->cb (omgh->cb_cls, 159 osr); 160 return; 161 } 162 osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED; 163 omgh->cb (omgh->cb_cls, 164 osr); 165 } 166 167 168 /** 169 * Function called when we're done processing the GET /private/orders/$ORDER 170 * request and we got an HTTP status of OK and the order was paid. Parse 171 * the response and call the callback. 172 * 173 * @param omgh handle for the request 174 * @param[in,out] osr HTTP response we got 175 */ 176 static void 177 handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh, 178 struct TALER_MERCHANT_OrderStatusResponse *osr) 179 { 180 uint32_t hc32; 181 const json_t *wire_details; 182 const json_t *refund_details; 183 struct GNUNET_JSON_Specification spec[] = { 184 GNUNET_JSON_spec_bool ("refunded", 185 &osr->details.ok.details.paid.refunded), 186 GNUNET_JSON_spec_bool ("refund_pending", 187 &osr->details.ok.details.paid.refund_pending), 188 GNUNET_JSON_spec_bool ("wired", 189 &osr->details.ok.details.paid.wired), 190 TALER_JSON_spec_amount_any ("deposit_total", 191 &osr->details.ok.details.paid.deposit_total), 192 TALER_JSON_spec_ec ("exchange_code", 193 &osr->details.ok.details.paid.exchange_ec), 194 GNUNET_JSON_spec_uint32 ("exchange_http_status", 195 &hc32), 196 TALER_JSON_spec_amount_any ("refund_amount", 197 &osr->details.ok.details.paid.refund_amount), 198 GNUNET_JSON_spec_object_const ( 199 "contract_terms", 200 &osr->details.ok.details.paid.contract_terms), 201 GNUNET_JSON_spec_array_const ("wire_details", 202 &wire_details), 203 GNUNET_JSON_spec_array_const ("refund_details", 204 &refund_details), 205 /* Only available since **v14** */ 206 GNUNET_JSON_spec_mark_optional ( 207 GNUNET_JSON_spec_timestamp ("last_payment", 208 &osr->details.ok.details.paid.last_payment), 209 NULL), 210 GNUNET_JSON_spec_end () 211 }; 212 213 if (GNUNET_OK != 214 GNUNET_JSON_parse (osr->hr.reply, 215 spec, 216 NULL, NULL)) 217 { 218 GNUNET_break_op (0); 219 osr->hr.http_status = 0; 220 osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 221 omgh->cb (omgh->cb_cls, 222 osr); 223 return; 224 } 225 osr->details.ok.status = TALER_MERCHANT_OSC_PAID; 226 227 osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32; 228 { 229 unsigned int wts_len = (unsigned int) json_array_size (wire_details); 230 unsigned int ref_len = (unsigned int) json_array_size (refund_details); 231 232 if ( (json_array_size (wire_details) != (size_t) wts_len) || 233 (wts_len > MAX_WIRE_DETAILS) ) 234 { 235 GNUNET_break (0); 236 osr->hr.http_status = 0; 237 osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; 238 omgh->cb (omgh->cb_cls, 239 osr); 240 return; 241 } 242 if ( (json_array_size (refund_details) != (size_t) ref_len) || 243 (ref_len > MAX_REFUND_DETAILS) ) 244 { 245 GNUNET_break (0); 246 osr->hr.http_status = 0; 247 osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; 248 omgh->cb (omgh->cb_cls, 249 osr); 250 return; 251 } 252 { 253 struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)]; 254 struct TALER_MERCHANT_RefundOrderDetail ref[GNUNET_NZL (ref_len)]; 255 256 for (unsigned int i = 0; i<wts_len; i++) 257 { 258 struct TALER_MERCHANT_WireTransfer *wt = &wts[i]; 259 const json_t *w = json_array_get (wire_details, 260 i); 261 struct GNUNET_JSON_Specification ispec[] = { 262 TALER_JSON_spec_web_url ("exchange_url", 263 &wt->exchange_url), 264 GNUNET_JSON_spec_fixed_auto ("wtid", 265 &wt->wtid), 266 GNUNET_JSON_spec_timestamp ("execution_time", 267 &wt->execution_time), 268 TALER_JSON_spec_amount_any ("amount", 269 &wt->total_amount), 270 GNUNET_JSON_spec_bool ("confirmed", 271 &wt->confirmed), 272 GNUNET_JSON_spec_end () 273 }; 274 275 if (GNUNET_OK != 276 GNUNET_JSON_parse (w, 277 ispec, 278 NULL, NULL)) 279 { 280 GNUNET_break_op (0); 281 osr->hr.http_status = 0; 282 osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 283 omgh->cb (omgh->cb_cls, 284 osr); 285 return; 286 } 287 } 288 289 for (unsigned int i = 0; i<ref_len; i++) 290 { 291 struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i]; 292 const json_t *w = json_array_get (refund_details, 293 i); 294 struct GNUNET_JSON_Specification ispec[] = { 295 TALER_JSON_spec_amount_any ("amount", 296 &ro->refund_amount), 297 GNUNET_JSON_spec_string ("reason", 298 &ro->reason), 299 GNUNET_JSON_spec_timestamp ("timestamp", 300 &ro->refund_time), 301 GNUNET_JSON_spec_end () 302 }; 303 304 if (GNUNET_OK != 305 GNUNET_JSON_parse (w, 306 ispec, 307 NULL, NULL)) 308 { 309 GNUNET_break_op (0); 310 osr->hr.http_status = 0; 311 osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 312 omgh->cb (omgh->cb_cls, 313 osr); 314 return; 315 } 316 } 317 318 osr->details.ok.details.paid.wts = wts; 319 osr->details.ok.details.paid.wts_len = wts_len; 320 osr->details.ok.details.paid.refunds = ref; 321 osr->details.ok.details.paid.refunds_len = ref_len; 322 omgh->cb (omgh->cb_cls, 323 osr); 324 } 325 } 326 } 327 328 329 /** 330 * Function called when we're done processing the GET /private/orders/$ORDER 331 * request. 332 * 333 * @param cls the `struct TALER_MERCHANT_OrderMerchantGetHandle` 334 * @param response_code HTTP response code, 0 on error 335 * @param response response body, NULL if not in JSON 336 */ 337 static void 338 handle_merchant_order_get_finished (void *cls, 339 long response_code, 340 const void *response) 341 { 342 struct TALER_MERCHANT_OrderMerchantGetHandle *omgh = cls; 343 const json_t *json = response; 344 const char *order_status; 345 struct TALER_MERCHANT_OrderStatusResponse osr = { 346 .hr.http_status = (unsigned int) response_code, 347 .hr.reply = json 348 }; 349 350 omgh->job = NULL; 351 switch (response_code) 352 { 353 case MHD_HTTP_OK: 354 /* see below */ 355 break; 356 case MHD_HTTP_ACCEPTED: 357 /* see below */ 358 omgh->cb (omgh->cb_cls, 359 &osr); 360 TALER_MERCHANT_merchant_order_get_cancel (omgh); 361 return; 362 case MHD_HTTP_UNAUTHORIZED: 363 osr.hr.ec = TALER_JSON_get_error_code (json); 364 osr.hr.hint = TALER_JSON_get_error_hint (json); 365 omgh->cb (omgh->cb_cls, 366 &osr); 367 TALER_MERCHANT_merchant_order_get_cancel (omgh); 368 return; 369 case MHD_HTTP_NOT_FOUND: 370 osr.hr.ec = TALER_JSON_get_error_code (json); 371 osr.hr.hint = TALER_JSON_get_error_hint (json); 372 omgh->cb (omgh->cb_cls, 373 &osr); 374 TALER_MERCHANT_merchant_order_get_cancel (omgh); 375 return; 376 case MHD_HTTP_GATEWAY_TIMEOUT: 377 osr.hr.ec = TALER_JSON_get_error_code (json); 378 osr.hr.hint = TALER_JSON_get_error_hint (json); 379 omgh->cb (omgh->cb_cls, 380 &osr); 381 TALER_MERCHANT_merchant_order_get_cancel (omgh); 382 return; 383 default: 384 osr.hr.ec = TALER_JSON_get_error_code (json); 385 osr.hr.hint = TALER_JSON_get_error_hint (json); 386 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 387 "Polling payment failed with HTTP status code %u/%d\n", 388 (unsigned int) response_code, 389 (int) osr.hr.ec); 390 GNUNET_break_op (0); 391 omgh->cb (omgh->cb_cls, 392 &osr); 393 TALER_MERCHANT_merchant_order_get_cancel (omgh); 394 return; 395 } 396 397 order_status = json_string_value (json_object_get (json, 398 "order_status")); 399 400 if (NULL == order_status) 401 { 402 GNUNET_break_op (0); 403 osr.hr.http_status = 0; 404 osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 405 omgh->cb (omgh->cb_cls, 406 &osr); 407 TALER_MERCHANT_merchant_order_get_cancel (omgh); 408 return; 409 } 410 411 if (0 == strcmp ("paid", 412 order_status)) 413 { 414 handle_paid (omgh, 415 &osr); 416 } 417 else if (0 == strcmp ("claimed", 418 order_status)) 419 { 420 handle_claimed (omgh, 421 &osr); 422 } 423 else if (0 == strcmp ("unpaid", 424 order_status)) 425 { 426 handle_unpaid (omgh, 427 &osr); 428 } 429 else 430 { 431 GNUNET_break_op (0); 432 osr.hr.http_status = 0; 433 osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 434 omgh->cb (omgh->cb_cls, 435 &osr); 436 } 437 TALER_MERCHANT_merchant_order_get_cancel (omgh); 438 } 439 440 441 struct TALER_MERCHANT_OrderMerchantGetHandle * 442 TALER_MERCHANT_merchant_order_get ( 443 struct GNUNET_CURL_Context *ctx, 444 const char *backend_url, 445 const char *order_id, 446 const char *session_id, 447 struct GNUNET_TIME_Relative timeout, 448 TALER_MERCHANT_OrderMerchantGetCallback cb, 449 void *cb_cls) 450 { 451 struct TALER_MERCHANT_OrderMerchantGetHandle *omgh; 452 unsigned int tms; 453 454 tms = (unsigned int) (timeout.rel_value_us 455 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 456 omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle); 457 omgh->ctx = ctx; 458 omgh->cb = cb; 459 omgh->cb_cls = cb_cls; 460 { 461 char *path; 462 char timeout_ms[32]; 463 464 GNUNET_snprintf (timeout_ms, 465 sizeof (timeout_ms), 466 "%u", 467 tms); 468 GNUNET_asprintf (&path, 469 "private/orders/%s", 470 order_id); 471 omgh->url = TALER_url_join (backend_url, 472 path, 473 "session_id", session_id, 474 "timeout_ms", (0 != tms) ? timeout_ms : NULL, 475 NULL); 476 GNUNET_free (path); 477 } 478 if (NULL == omgh->url) 479 { 480 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 481 "Could not construct request URL.\n"); 482 GNUNET_free (omgh); 483 return NULL; 484 } 485 486 { 487 CURL *eh; 488 489 eh = TALER_MERCHANT_curl_easy_get_ (omgh->url); 490 if (NULL == eh) 491 { 492 GNUNET_break (0); 493 GNUNET_free (omgh->url); 494 GNUNET_free (omgh); 495 return NULL; 496 } 497 if (0 != tms) 498 { 499 GNUNET_break (CURLE_OK == 500 curl_easy_setopt (eh, 501 CURLOPT_TIMEOUT_MS, 502 (long) (tms + 100L))); 503 } 504 505 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 506 "Getting order status from %s\n", 507 omgh->url); 508 if (NULL == (omgh->job = 509 GNUNET_CURL_job_add (ctx, 510 eh, 511 &handle_merchant_order_get_finished, 512 omgh))) 513 { 514 GNUNET_break (0); 515 GNUNET_free (omgh->url); 516 GNUNET_free (omgh); 517 return NULL; 518 } 519 } 520 return omgh; 521 } 522 523 524 void 525 TALER_MERCHANT_merchant_order_get_cancel ( 526 struct TALER_MERCHANT_OrderMerchantGetHandle *omgh) 527 { 528 if (NULL != omgh->job) 529 { 530 GNUNET_CURL_job_cancel (omgh->job); 531 omgh->job = NULL; 532 } 533 GNUNET_free (omgh->url); 534 GNUNET_free (omgh); 535 } 536 537 538 /* end of merchant_api_merchant_get_order.c */