merchant_api_post-orders-ORDER_ID-refund.c (11052B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Lesser General Public License as 7 published by the Free Software Foundation; either version 2.1, 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 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General 16 Public License along with TALER; see the file COPYING.LGPL. 17 If not, see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file merchant_api_post-orders-ORDER_ID-refund-new.c 21 * @brief Implementation of the POST /orders/$ORDER_ID/refund request 22 * @author Christian Grothoff 23 */ 24 #include "taler/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/taler-merchant/post-orders-ORDER_ID-refund.h> 31 #include "merchant_api_curl_defaults.h" 32 #include "merchant_api_common.h" 33 #include <taler/taler_json_lib.h> 34 #include <taler/taler_curl_lib.h> 35 #include <taler/taler_signatures.h> 36 37 38 /** 39 * Maximum number of refunds we return. 40 */ 41 #define MAX_REFUNDS 1024 42 43 44 /** 45 * Handle for a POST /orders/$ORDER_ID/refund operation. 46 */ 47 struct TALER_MERCHANT_PostOrdersRefundHandle 48 { 49 /** 50 * Base URL of the merchant backend. 51 */ 52 char *base_url; 53 54 /** 55 * The full URL for this request. 56 */ 57 char *url; 58 59 /** 60 * Handle for the request. 61 */ 62 struct GNUNET_CURL_Job *job; 63 64 /** 65 * Function to call with the result. 66 */ 67 TALER_MERCHANT_PostOrdersRefundCallback cb; 68 69 /** 70 * Closure for @a cb. 71 */ 72 TALER_MERCHANT_POST_ORDERS_REFUND_RESULT_CLOSURE *cb_cls; 73 74 /** 75 * Reference to the execution context. 76 */ 77 struct GNUNET_CURL_Context *ctx; 78 79 /** 80 * Minor context that holds body and headers. 81 */ 82 struct TALER_CURL_PostContext post_ctx; 83 84 /** 85 * Order identifier. 86 */ 87 char *order_id; 88 89 /** 90 * Hash of the contract terms. 91 */ 92 struct TALER_PrivateContractHashP h_contract_terms; 93 }; 94 95 96 /** 97 * Function called when we're done processing the 98 * HTTP POST /orders/$ORDER_ID/refund request. 99 * 100 * @param cls the `struct TALER_MERCHANT_PostOrdersRefundHandle` 101 * @param response_code HTTP response code, 0 on error 102 * @param response response body, NULL if not in JSON 103 */ 104 static void 105 handle_refund_finished (void *cls, 106 long response_code, 107 const void *response) 108 { 109 struct TALER_MERCHANT_PostOrdersRefundHandle *porh = cls; 110 const json_t *json = response; 111 struct TALER_MERCHANT_PostOrdersRefundResponse orr = { 112 .hr.http_status = (unsigned int) response_code, 113 .hr.reply = json 114 }; 115 116 porh->job = NULL; 117 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 118 "POST /orders/$ID/refund completed with response code %u\n", 119 (unsigned int) response_code); 120 switch (response_code) 121 { 122 case 0: 123 orr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 124 break; 125 case MHD_HTTP_OK: 126 { 127 const json_t *refunds; 128 unsigned int refund_len; 129 struct GNUNET_JSON_Specification spec[] = { 130 GNUNET_JSON_spec_array_const ( 131 "refunds", 132 &refunds), 133 TALER_JSON_spec_amount_any ( 134 "refund_amount", 135 &orr.details.ok.refund_amount), 136 GNUNET_JSON_spec_fixed_auto ( 137 "merchant_pub", 138 &orr.details.ok.merchant_pub), 139 GNUNET_JSON_spec_end () 140 }; 141 142 if (GNUNET_OK != 143 GNUNET_JSON_parse (json, 144 spec, 145 NULL, NULL)) 146 { 147 GNUNET_break_op (0); 148 orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 149 orr.hr.http_status = 0; 150 break; 151 } 152 refund_len = (unsigned int) json_array_size (refunds); 153 if ( (json_array_size (refunds) != (size_t) refund_len) || 154 (refund_len > MAX_REFUNDS) ) 155 { 156 GNUNET_break (0); 157 orr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; 158 orr.hr.http_status = 0; 159 break; 160 } 161 { 162 struct TALER_MERCHANT_PostOrdersRefundDetail rds[GNUNET_NZL ( 163 refund_len)]; 164 165 memset (rds, 166 0, 167 sizeof (rds)); 168 for (unsigned int i = 0; i < refund_len; i++) 169 { 170 struct TALER_MERCHANT_PostOrdersRefundDetail *rd = &rds[i]; 171 const json_t *jrefund = json_array_get (refunds, 172 i); 173 const char *refund_status_type; 174 uint32_t exchange_status; 175 uint32_t eec = 0; 176 struct GNUNET_JSON_Specification espec[] = { 177 GNUNET_JSON_spec_string ("type", 178 &refund_status_type), 179 GNUNET_JSON_spec_uint32 ("exchange_status", 180 &exchange_status), 181 GNUNET_JSON_spec_uint64 ("rtransaction_id", 182 &rd->rtransaction_id), 183 GNUNET_JSON_spec_fixed_auto ("coin_pub", 184 &rd->coin_pub), 185 TALER_JSON_spec_amount_any ("refund_amount", 186 &rd->refund_amount), 187 GNUNET_JSON_spec_mark_optional ( 188 GNUNET_JSON_spec_object_const ("exchange_reply", 189 &rd->exchange_reply), 190 NULL), 191 GNUNET_JSON_spec_mark_optional ( 192 GNUNET_JSON_spec_uint32 ("exchange_code", 193 &eec), 194 NULL), 195 GNUNET_JSON_spec_end () 196 }; 197 198 if (GNUNET_OK != 199 GNUNET_JSON_parse (jrefund, 200 espec, 201 NULL, NULL)) 202 { 203 GNUNET_break_op (0); 204 orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 205 orr.hr.http_status = 0; 206 goto finish; 207 } 208 209 rd->exchange_http_status = exchange_status; 210 rd->ec = (enum TALER_ErrorCode) eec; 211 switch (exchange_status) 212 { 213 case MHD_HTTP_OK: 214 { 215 struct GNUNET_JSON_Specification rspec[] = { 216 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 217 &rd->exchange_sig), 218 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 219 &rd->exchange_pub), 220 GNUNET_JSON_spec_end () 221 }; 222 223 if (GNUNET_OK != 224 GNUNET_JSON_parse (jrefund, 225 rspec, 226 NULL, 227 NULL)) 228 { 229 GNUNET_break_op (0); 230 orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 231 orr.hr.http_status = 0; 232 goto finish; 233 } 234 if (0 != strcmp ("success", 235 refund_status_type)) 236 { 237 GNUNET_break_op (0); 238 orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 239 orr.hr.http_status = 0; 240 goto finish; 241 } 242 } 243 break; 244 default: 245 if (0 != strcmp ("failure", 246 refund_status_type)) 247 { 248 GNUNET_break_op (0); 249 orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 250 orr.hr.http_status = 0; 251 goto finish; 252 } 253 } 254 } 255 256 orr.details.ok.refunds = rds; 257 orr.details.ok.num_refunds = refund_len; 258 porh->cb (porh->cb_cls, 259 &orr); 260 TALER_MERCHANT_post_orders_refund_cancel (porh); 261 return; 262 } 263 } 264 break; 265 case MHD_HTTP_NO_CONTENT: 266 break; 267 case MHD_HTTP_NOT_FOUND: 268 orr.hr.ec = TALER_JSON_get_error_code (json); 269 orr.hr.hint = TALER_JSON_get_error_hint (json); 270 break; 271 default: 272 GNUNET_break_op (0); 273 TALER_MERCHANT_parse_error_details_ (json, 274 response_code, 275 &orr.hr); 276 break; 277 } 278 finish: 279 porh->cb (porh->cb_cls, 280 &orr); 281 TALER_MERCHANT_post_orders_refund_cancel (porh); 282 } 283 284 285 struct TALER_MERCHANT_PostOrdersRefundHandle * 286 TALER_MERCHANT_post_orders_refund_create ( 287 struct GNUNET_CURL_Context *ctx, 288 const char *url, 289 const char *order_id, 290 const struct TALER_PrivateContractHashP *h_contract_terms) 291 { 292 struct TALER_MERCHANT_PostOrdersRefundHandle *porh; 293 294 porh = GNUNET_new (struct TALER_MERCHANT_PostOrdersRefundHandle); 295 porh->ctx = ctx; 296 porh->base_url = GNUNET_strdup (url); 297 porh->order_id = GNUNET_strdup (order_id); 298 porh->h_contract_terms = *h_contract_terms; 299 return porh; 300 } 301 302 303 enum TALER_ErrorCode 304 TALER_MERCHANT_post_orders_refund_start ( 305 struct TALER_MERCHANT_PostOrdersRefundHandle *porh, 306 TALER_MERCHANT_PostOrdersRefundCallback cb, 307 TALER_MERCHANT_POST_ORDERS_REFUND_RESULT_CLOSURE *cb_cls) 308 { 309 json_t *req_obj; 310 CURL *eh; 311 312 porh->cb = cb; 313 porh->cb_cls = cb_cls; 314 { 315 char *path; 316 317 GNUNET_asprintf (&path, 318 "orders/%s/refund", 319 porh->order_id); 320 porh->url = TALER_url_join (porh->base_url, 321 path, 322 NULL); 323 GNUNET_free (path); 324 } 325 if (NULL == porh->url) 326 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 327 req_obj = GNUNET_JSON_PACK ( 328 GNUNET_JSON_pack_data_auto ("h_contract", 329 &porh->h_contract_terms)); 330 eh = TALER_MERCHANT_curl_easy_get_ (porh->url); 331 if ( (NULL == eh) || 332 (GNUNET_OK != 333 TALER_curl_easy_post (&porh->post_ctx, 334 eh, 335 req_obj)) ) 336 { 337 GNUNET_break (0); 338 json_decref (req_obj); 339 if (NULL != eh) 340 curl_easy_cleanup (eh); 341 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 342 } 343 json_decref (req_obj); 344 porh->job = GNUNET_CURL_job_add2 (porh->ctx, 345 eh, 346 porh->post_ctx.headers, 347 &handle_refund_finished, 348 porh); 349 if (NULL == porh->job) 350 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 351 return TALER_EC_NONE; 352 } 353 354 355 void 356 TALER_MERCHANT_post_orders_refund_cancel ( 357 struct TALER_MERCHANT_PostOrdersRefundHandle *porh) 358 { 359 if (NULL != porh->job) 360 { 361 GNUNET_CURL_job_cancel (porh->job); 362 porh->job = NULL; 363 } 364 TALER_curl_easy_post_finished (&porh->post_ctx); 365 GNUNET_free (porh->order_id); 366 GNUNET_free (porh->url); 367 GNUNET_free (porh->base_url); 368 GNUNET_free (porh); 369 } 370 371 372 /* end of merchant_api_post-orders-ORDER_ID-refund-new.c */