exchange_api_post-coins-COIN_PUB-refund.c (12079B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_post-coins-COIN_PUB-refund.c 19 * @brief Implementation of the /refund request of the exchange's HTTP API 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <jansson.h> 24 #include <microhttpd.h> /* just for HTTP status codes */ 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_json_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include "taler/taler_json_lib.h" 29 #include "taler/taler_exchange_service.h" 30 #include "taler/taler-exchange/post-coins-COIN_PUB-refund.h" 31 #include "exchange_api_handle.h" 32 #include "taler/taler_signatures.h" 33 #include "exchange_api_curl_defaults.h" 34 35 36 /** 37 * @brief A POST /coins/$COIN_PUB/refund Handle 38 */ 39 struct TALER_EXCHANGE_PostCoinsRefundHandle 40 { 41 42 /** 43 * The keys of the exchange this request handle will use 44 */ 45 struct TALER_EXCHANGE_Keys *keys; 46 47 /** 48 * The exchange base URL. 49 */ 50 char *base_url; 51 52 /** 53 * The full URL for this request, set during _start. 54 */ 55 char *url; 56 57 /** 58 * Reference to the execution context. 59 */ 60 struct GNUNET_CURL_Context *ctx; 61 62 /** 63 * Context for #TEH_curl_easy_post(). Keeps the data that must 64 * persist for Curl to make the upload. 65 */ 66 struct TALER_CURL_PostContext post_ctx; 67 68 /** 69 * Handle for the request. 70 */ 71 struct GNUNET_CURL_Job *job; 72 73 /** 74 * Function to call with the result. 75 */ 76 TALER_EXCHANGE_PostCoinsRefundCallback cb; 77 78 /** 79 * Closure for @e cb. 80 */ 81 TALER_EXCHANGE_POST_COINS_REFUND_RESULT_CLOSURE *cb_cls; 82 83 /** 84 * Hash over the proposal data to identify the contract 85 * which is being refunded. 86 */ 87 struct TALER_PrivateContractHashP h_contract_terms; 88 89 /** 90 * The coin's public key. This is the value that must have been 91 * signed (blindly) by the Exchange. 92 */ 93 struct TALER_CoinSpendPublicKeyP coin_pub; 94 95 /** 96 * The Merchant's public key. 97 */ 98 struct TALER_MerchantPublicKeyP merchant_pub; 99 100 /** 101 * The merchant's private key (for signing). 102 */ 103 struct TALER_MerchantPrivateKeyP merchant_priv; 104 105 /** 106 * Merchant-generated transaction ID for the refund. 107 */ 108 uint64_t rtransaction_id; 109 110 /** 111 * Amount to be refunded. 112 */ 113 struct TALER_Amount refund_amount; 114 115 }; 116 117 118 /** 119 * Verify that the signature on the "200 OK" response 120 * from the exchange is valid. 121 * 122 * @param[in,out] rh refund handle (refund fee added) 123 * @param json json reply with the signature 124 * @param[out] exchange_pub set to the exchange's public key 125 * @param[out] exchange_sig set to the exchange's signature 126 * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not 127 */ 128 static enum GNUNET_GenericReturnValue 129 verify_refund_signature_ok (struct TALER_EXCHANGE_PostCoinsRefundHandle *rh, 130 const json_t *json, 131 struct TALER_ExchangePublicKeyP *exchange_pub, 132 struct TALER_ExchangeSignatureP *exchange_sig) 133 { 134 struct GNUNET_JSON_Specification spec[] = { 135 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 136 exchange_sig), 137 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 138 exchange_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 return GNUNET_SYSERR; 149 } 150 if (GNUNET_OK != 151 TALER_EXCHANGE_test_signing_key (rh->keys, 152 exchange_pub)) 153 { 154 GNUNET_break_op (0); 155 return GNUNET_SYSERR; 156 } 157 if (GNUNET_OK != 158 TALER_exchange_online_refund_confirmation_verify ( 159 &rh->h_contract_terms, 160 &rh->coin_pub, 161 &rh->merchant_pub, 162 rh->rtransaction_id, 163 &rh->refund_amount, 164 exchange_pub, 165 exchange_sig)) 166 { 167 GNUNET_break_op (0); 168 return GNUNET_SYSERR; 169 } 170 return GNUNET_OK; 171 } 172 173 174 /** 175 * Function called when we're done processing the 176 * HTTP /refund request. 177 * 178 * @param cls the `struct TALER_EXCHANGE_PostCoinsRefundHandle` 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_refund_finished (void *cls, 184 long response_code, 185 const void *response) 186 { 187 struct TALER_EXCHANGE_PostCoinsRefundHandle *rh = cls; 188 const json_t *j = response; 189 struct TALER_EXCHANGE_PostCoinsRefundResponse rr = { 190 .hr.reply = j, 191 .hr.http_status = (unsigned int) response_code 192 }; 193 194 rh->job = NULL; 195 switch (response_code) 196 { 197 case 0: 198 rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 199 break; 200 case MHD_HTTP_OK: 201 if (GNUNET_OK != 202 verify_refund_signature_ok (rh, 203 j, 204 &rr.details.ok.exchange_pub, 205 &rr.details.ok.exchange_sig)) 206 { 207 GNUNET_break_op (0); 208 rr.hr.http_status = 0; 209 rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE; 210 } 211 break; 212 case MHD_HTTP_BAD_REQUEST: 213 /* This should never happen, either us or the exchange is buggy 214 (or API version conflict); also can happen if the currency 215 differs (which we should obviously never support). 216 Just pass JSON reply to the application */ 217 rr.hr.ec = TALER_JSON_get_error_code (j); 218 rr.hr.hint = TALER_JSON_get_error_hint (j); 219 break; 220 case MHD_HTTP_FORBIDDEN: 221 /* Nothing really to verify, exchange says one of the signatures is 222 invalid; as we checked them, this should never happen, we 223 should pass the JSON reply to the application */ 224 rr.hr.ec = TALER_JSON_get_error_code (j); 225 rr.hr.hint = TALER_JSON_get_error_hint (j); 226 break; 227 case MHD_HTTP_NOT_FOUND: 228 /* Nothing really to verify, this should never 229 happen, we should pass the JSON reply to the application */ 230 rr.hr.ec = TALER_JSON_get_error_code (j); 231 rr.hr.hint = TALER_JSON_get_error_hint (j); 232 break; 233 case MHD_HTTP_CONFLICT: 234 /* Requested total refunds exceed deposited amount */ 235 rr.hr.ec = TALER_JSON_get_error_code (j); 236 rr.hr.hint = TALER_JSON_get_error_hint (j); 237 break; 238 case MHD_HTTP_GONE: 239 /* Kind of normal: the money was already sent to the merchant 240 (it was too late for the refund). */ 241 rr.hr.ec = TALER_JSON_get_error_code (j); 242 rr.hr.hint = TALER_JSON_get_error_hint (j); 243 break; 244 case MHD_HTTP_FAILED_DEPENDENCY: 245 rr.hr.ec = TALER_JSON_get_error_code (j); 246 rr.hr.hint = TALER_JSON_get_error_hint (j); 247 break; 248 case MHD_HTTP_INTERNAL_SERVER_ERROR: 249 /* Server had an internal issue; we should retry, but this API 250 leaves this to the application */ 251 rr.hr.ec = TALER_JSON_get_error_code (j); 252 rr.hr.hint = TALER_JSON_get_error_hint (j); 253 break; 254 default: 255 /* unexpected response code */ 256 GNUNET_break_op (0); 257 rr.hr.ec = TALER_JSON_get_error_code (j); 258 rr.hr.hint = TALER_JSON_get_error_hint (j); 259 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 260 "Unexpected response code %u/%d for exchange refund\n", 261 (unsigned int) response_code, 262 rr.hr.ec); 263 break; 264 } 265 if (NULL != rh->cb) 266 { 267 rh->cb (rh->cb_cls, 268 &rr); 269 rh->cb = NULL; 270 } 271 TALER_EXCHANGE_post_coins_refund_cancel (rh); 272 } 273 274 275 struct TALER_EXCHANGE_PostCoinsRefundHandle * 276 TALER_EXCHANGE_post_coins_refund_create ( 277 struct GNUNET_CURL_Context *ctx, 278 const char *url, 279 struct TALER_EXCHANGE_Keys *keys, 280 const struct TALER_Amount *amount, 281 const struct TALER_PrivateContractHashP *h_contract_terms, 282 const struct TALER_CoinSpendPublicKeyP *coin_pub, 283 uint64_t rtransaction_id, 284 const struct TALER_MerchantPrivateKeyP *merchant_priv) 285 { 286 struct TALER_EXCHANGE_PostCoinsRefundHandle *rh; 287 288 rh = GNUNET_new (struct TALER_EXCHANGE_PostCoinsRefundHandle); 289 rh->ctx = ctx; 290 rh->base_url = GNUNET_strdup (url); 291 rh->keys = TALER_EXCHANGE_keys_incref (keys); 292 rh->refund_amount = *amount; 293 rh->h_contract_terms = *h_contract_terms; 294 rh->coin_pub = *coin_pub; 295 rh->rtransaction_id = rtransaction_id; 296 rh->merchant_priv = *merchant_priv; 297 GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, 298 &rh->merchant_pub.eddsa_pub); 299 return rh; 300 } 301 302 303 enum TALER_ErrorCode 304 TALER_EXCHANGE_post_coins_refund_start ( 305 struct TALER_EXCHANGE_PostCoinsRefundHandle *rh, 306 TALER_EXCHANGE_PostCoinsRefundCallback cb, 307 TALER_EXCHANGE_POST_COINS_REFUND_RESULT_CLOSURE *cb_cls) 308 { 309 struct TALER_MerchantSignatureP merchant_sig; 310 json_t *refund_obj; 311 CURL *eh; 312 char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; 313 314 rh->cb = cb; 315 rh->cb_cls = cb_cls; 316 TALER_merchant_refund_sign (&rh->coin_pub, 317 &rh->h_contract_terms, 318 rh->rtransaction_id, 319 &rh->refund_amount, 320 &rh->merchant_priv, 321 &merchant_sig); 322 { 323 char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; 324 char *end; 325 326 end = GNUNET_STRINGS_data_to_string ( 327 &rh->coin_pub, 328 sizeof (struct TALER_CoinSpendPublicKeyP), 329 pub_str, 330 sizeof (pub_str)); 331 *end = '\0'; 332 GNUNET_snprintf (arg_str, 333 sizeof (arg_str), 334 "coins/%s/refund", 335 pub_str); 336 } 337 refund_obj = GNUNET_JSON_PACK ( 338 TALER_JSON_pack_amount ("refund_amount", 339 &rh->refund_amount), 340 GNUNET_JSON_pack_data_auto ("h_contract_terms", 341 &rh->h_contract_terms), 342 GNUNET_JSON_pack_uint64 ("rtransaction_id", 343 rh->rtransaction_id), 344 GNUNET_JSON_pack_data_auto ("merchant_pub", 345 &rh->merchant_pub), 346 GNUNET_JSON_pack_data_auto ("merchant_sig", 347 &merchant_sig)); 348 rh->url = TALER_url_join (rh->base_url, 349 arg_str, 350 NULL); 351 if (NULL == rh->url) 352 { 353 json_decref (refund_obj); 354 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 355 } 356 eh = TALER_EXCHANGE_curl_easy_get_ (rh->url); 357 if ( (NULL == eh) || 358 (GNUNET_OK != 359 TALER_curl_easy_post (&rh->post_ctx, 360 eh, 361 refund_obj)) ) 362 { 363 GNUNET_break (0); 364 if (NULL != eh) 365 curl_easy_cleanup (eh); 366 json_decref (refund_obj); 367 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 368 } 369 json_decref (refund_obj); 370 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 371 "URL for refund: `%s'\n", 372 rh->url); 373 rh->job = GNUNET_CURL_job_add2 (rh->ctx, 374 eh, 375 rh->post_ctx.headers, 376 &handle_refund_finished, 377 rh); 378 if (NULL == rh->job) 379 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 380 return TALER_EC_NONE; 381 } 382 383 384 void 385 TALER_EXCHANGE_post_coins_refund_cancel ( 386 struct TALER_EXCHANGE_PostCoinsRefundHandle *rh) 387 { 388 if (NULL != rh->job) 389 { 390 GNUNET_CURL_job_cancel (rh->job); 391 rh->job = NULL; 392 } 393 GNUNET_free (rh->url); 394 GNUNET_free (rh->base_url); 395 TALER_curl_easy_post_finished (&rh->post_ctx); 396 TALER_EXCHANGE_keys_decref (rh->keys); 397 GNUNET_free (rh); 398 } 399 400 401 /* end of exchange_api_post-coins-COIN_PUB-refund.c */