merchant_api_wallet_post_order_refund.c (10359B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-2023 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_wallet_post_order_refund.c 19 * @brief Implementation of the (public) POST /orders/ID/refund request 20 * @author Jonathan Buchanan 21 */ 22 #include "platform.h" 23 #include <curl/curl.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include "taler_merchant_service.h" 29 #include "merchant_api_common.h" 30 #include "merchant_api_curl_defaults.h" 31 #include <taler/taler_json_lib.h> 32 #include <taler/taler_signatures.h> 33 #include <taler/taler_curl_lib.h> 34 35 /** 36 * Maximum number of refunds we return. 37 */ 38 #define MAX_REFUNDS 1024 39 40 /** 41 * Handle for a (public) POST /orders/ID/refund operation. 42 */ 43 struct TALER_MERCHANT_WalletOrderRefundHandle 44 { 45 /** 46 * Complete URL where the backend offers /refund 47 */ 48 char *url; 49 50 /** 51 * Minor context that holds body and headers. 52 */ 53 struct TALER_CURL_PostContext post_ctx; 54 55 /** 56 * The CURL context to connect to the backend 57 */ 58 struct GNUNET_CURL_Context *ctx; 59 60 /** 61 * The callback to pass the backend response to 62 */ 63 TALER_MERCHANT_WalletRefundCallback cb; 64 65 /** 66 * Clasure to pass to the callback 67 */ 68 void *cb_cls; 69 70 /** 71 * Handle for the request 72 */ 73 struct GNUNET_CURL_Job *job; 74 }; 75 76 77 /** 78 * Callback to process (public) POST /orders/ID/refund response 79 * 80 * @param cls the `struct TALER_MERCHANT_OrderRefundHandle` 81 * @param response_code HTTP response code, 0 on error 82 * @param response response body, NULL if not JSON 83 */ 84 static void 85 handle_refund_finished (void *cls, 86 long response_code, 87 const void *response) 88 { 89 struct TALER_MERCHANT_WalletOrderRefundHandle *orh = cls; 90 const json_t *json = response; 91 struct TALER_MERCHANT_WalletRefundResponse wrr = { 92 .hr.http_status = (unsigned int) response_code, 93 .hr.reply = json 94 }; 95 96 orh->job = NULL; 97 switch (response_code) 98 { 99 case 0: 100 wrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 101 break; 102 case MHD_HTTP_OK: 103 { 104 const json_t *refunds; 105 unsigned int refund_len; 106 struct GNUNET_JSON_Specification spec[] = { 107 TALER_JSON_spec_amount_any ( 108 "refund_amount", 109 &wrr.details.ok.refund_amount), 110 GNUNET_JSON_spec_array_const ( 111 "refunds", 112 &refunds), 113 GNUNET_JSON_spec_fixed_auto ( 114 "merchant_pub", 115 &wrr.details.ok.merchant_pub), 116 GNUNET_JSON_spec_end () 117 }; 118 119 if (GNUNET_OK != 120 GNUNET_JSON_parse (json, 121 spec, 122 NULL, NULL)) 123 { 124 GNUNET_break_op (0); 125 wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 126 wrr.hr.http_status = 0; 127 break; 128 } 129 refund_len = json_array_size (refunds); 130 if ( (json_array_size (refunds) != (size_t) refund_len) || 131 (refund_len > MAX_REFUNDS) ) 132 { 133 GNUNET_break (0); 134 wrr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; 135 wrr.hr.http_status = 0; 136 break; 137 } 138 { 139 struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (refund_len)]; 140 141 memset (rds, 142 0, 143 sizeof (rds)); 144 for (unsigned int i = 0; i<refund_len; i++) 145 { 146 struct TALER_MERCHANT_RefundDetail *rd = &rds[i]; 147 const json_t *jrefund = json_array_get (refunds, 148 i); 149 const char *refund_status_type; 150 uint32_t exchange_status; 151 uint32_t eec = 0; 152 struct GNUNET_JSON_Specification espec[] = { 153 GNUNET_JSON_spec_string ("type", 154 &refund_status_type), 155 GNUNET_JSON_spec_uint32 ("exchange_status", 156 &exchange_status), 157 GNUNET_JSON_spec_uint64 ("rtransaction_id", 158 &rd->rtransaction_id), 159 GNUNET_JSON_spec_fixed_auto ("coin_pub", 160 &rd->coin_pub), 161 TALER_JSON_spec_amount_any ("refund_amount", 162 &rd->refund_amount), 163 GNUNET_JSON_spec_mark_optional ( 164 GNUNET_JSON_spec_object_const ("exchange_reply", 165 &rd->hr.reply), 166 NULL), 167 GNUNET_JSON_spec_mark_optional ( 168 GNUNET_JSON_spec_uint32 ("exchange_code", 169 &eec), 170 NULL), 171 GNUNET_JSON_spec_end () 172 }; 173 174 if (GNUNET_OK != 175 GNUNET_JSON_parse (jrefund, 176 espec, 177 NULL, NULL)) 178 { 179 GNUNET_break_op (0); 180 wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 181 wrr.hr.http_status = 0; 182 goto finish; 183 } 184 185 rd->hr.http_status = exchange_status; 186 rd->hr.ec = (enum TALER_ErrorCode) eec; 187 switch (exchange_status) 188 { 189 case MHD_HTTP_OK: 190 { 191 struct GNUNET_JSON_Specification rspec[] = { 192 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 193 &rd->details.ok.exchange_sig), 194 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 195 &rd->details.ok.exchange_pub), 196 GNUNET_JSON_spec_end () 197 }; 198 199 if (GNUNET_OK != 200 GNUNET_JSON_parse (jrefund, 201 rspec, 202 NULL, 203 NULL)) 204 { 205 GNUNET_break_op (0); 206 wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 207 wrr.hr.http_status = 0; 208 goto finish; 209 } 210 /* check that type field is correct */ 211 if (0 != strcmp ("success", 212 refund_status_type)) 213 { 214 GNUNET_break_op (0); 215 wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 216 wrr.hr.http_status = 0; 217 goto finish; 218 } 219 } 220 break; /* end MHD_HTTP_OK */ 221 default: 222 if (0 != strcmp ("failure", 223 refund_status_type)) 224 { 225 GNUNET_break_op (0); 226 wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 227 wrr.hr.http_status = 0; 228 goto finish; 229 } 230 } /* switch on exchange status code */ 231 } /* for all refunds */ 232 233 wrr.details.ok.refunds = rds; 234 wrr.details.ok.refunds_length = refund_len; 235 orh->cb (orh->cb_cls, 236 &wrr); 237 TALER_MERCHANT_wallet_post_order_refund_cancel (orh); 238 return; 239 } /* end 'rds' scope */ 240 } /* case MHD_HTTP_OK */ 241 break; 242 case MHD_HTTP_NO_CONTENT: 243 break; 244 case MHD_HTTP_CONFLICT: 245 case MHD_HTTP_NOT_FOUND: 246 wrr.hr.ec = TALER_JSON_get_error_code (json); 247 wrr.hr.hint = TALER_JSON_get_error_hint (json); 248 break; 249 default: 250 GNUNET_break_op (0); /* unexpected status code */ 251 TALER_MERCHANT_parse_error_details_ (json, 252 response_code, 253 &wrr.hr); 254 break; 255 } 256 finish: 257 orh->cb (orh->cb_cls, 258 &wrr); 259 TALER_MERCHANT_wallet_post_order_refund_cancel (orh); 260 } 261 262 263 struct TALER_MERCHANT_WalletOrderRefundHandle * 264 TALER_MERCHANT_wallet_post_order_refund ( 265 struct GNUNET_CURL_Context *ctx, 266 const char *backend_url, 267 const char *order_id, 268 const struct TALER_PrivateContractHashP *h_contract_terms, 269 TALER_MERCHANT_WalletRefundCallback cb, 270 void *cb_cls) 271 { 272 struct TALER_MERCHANT_WalletOrderRefundHandle *orh; 273 json_t *req; 274 CURL *eh; 275 276 orh = GNUNET_new (struct TALER_MERCHANT_WalletOrderRefundHandle); 277 orh->ctx = ctx; 278 orh->cb = cb; 279 orh->cb_cls = cb_cls; 280 { 281 char *path; 282 283 GNUNET_asprintf (&path, 284 "orders/%s/refund", 285 order_id); 286 orh->url = TALER_url_join (backend_url, 287 path, 288 NULL); 289 GNUNET_free (path); 290 } 291 if (NULL == orh->url) 292 { 293 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 294 "Could not construct request URL.\n"); 295 GNUNET_free (orh); 296 return NULL; 297 } 298 req = GNUNET_JSON_PACK ( 299 GNUNET_JSON_pack_data_auto ("h_contract", 300 h_contract_terms)); 301 eh = TALER_MERCHANT_curl_easy_get_ (orh->url); 302 if (GNUNET_OK != 303 TALER_curl_easy_post (&orh->post_ctx, 304 eh, 305 req)) 306 { 307 GNUNET_break (0); 308 json_decref (req); 309 curl_easy_cleanup (eh); 310 GNUNET_free (orh->url); 311 GNUNET_free (orh); 312 return NULL; 313 } 314 json_decref (req); 315 orh->job = GNUNET_CURL_job_add2 (ctx, 316 eh, 317 orh->post_ctx.headers, 318 &handle_refund_finished, 319 orh); 320 if (NULL == orh->job) 321 { 322 GNUNET_free (orh->url); 323 GNUNET_free (orh); 324 return NULL; 325 } 326 return orh; 327 } 328 329 330 void 331 TALER_MERCHANT_wallet_post_order_refund_cancel ( 332 struct TALER_MERCHANT_WalletOrderRefundHandle *orh) 333 { 334 if (NULL != orh->job) 335 { 336 GNUNET_CURL_job_cancel (orh->job); 337 orh->job = NULL; 338 } 339 TALER_curl_easy_post_finished (&orh->post_ctx); 340 GNUNET_free (orh->url); 341 GNUNET_free (orh); 342 } 343 344 345 /* end of merchant_api_wallet_post_order_refund.c */