Line data Source code
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" /* UNNECESSARY? */
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" /* UNNECESSARY? */
30 : #include "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 10 : 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 10 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
136 : exchange_sig),
137 10 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
138 : exchange_pub),
139 10 : GNUNET_JSON_spec_end ()
140 : };
141 :
142 10 : if (GNUNET_OK !=
143 10 : GNUNET_JSON_parse (json,
144 : spec,
145 : NULL, NULL))
146 : {
147 0 : GNUNET_break_op (0);
148 0 : return GNUNET_SYSERR;
149 : }
150 10 : if (GNUNET_OK !=
151 10 : TALER_EXCHANGE_test_signing_key (rh->keys,
152 : exchange_pub))
153 : {
154 0 : GNUNET_break_op (0);
155 0 : return GNUNET_SYSERR;
156 : }
157 10 : if (GNUNET_OK !=
158 10 : TALER_exchange_online_refund_confirmation_verify (
159 10 : &rh->h_contract_terms,
160 10 : &rh->coin_pub,
161 10 : &rh->merchant_pub,
162 : rh->rtransaction_id,
163 10 : &rh->refund_amount,
164 : exchange_pub,
165 : exchange_sig))
166 : {
167 0 : GNUNET_break_op (0);
168 0 : return GNUNET_SYSERR;
169 : }
170 10 : 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 14 : handle_refund_finished (void *cls,
184 : long response_code,
185 : const void *response)
186 : {
187 14 : struct TALER_EXCHANGE_PostCoinsRefundHandle *rh = cls;
188 14 : const json_t *j = response;
189 14 : struct TALER_EXCHANGE_PostCoinsRefundResponse rr = {
190 : .hr.reply = j,
191 14 : .hr.http_status = (unsigned int) response_code
192 : };
193 :
194 14 : rh->job = NULL;
195 14 : switch (response_code)
196 : {
197 0 : case 0:
198 0 : rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
199 0 : break;
200 10 : case MHD_HTTP_OK:
201 10 : if (GNUNET_OK !=
202 10 : verify_refund_signature_ok (rh,
203 : j,
204 : &rr.details.ok.exchange_pub,
205 : &rr.details.ok.exchange_sig))
206 : {
207 0 : GNUNET_break_op (0);
208 0 : rr.hr.http_status = 0;
209 0 : rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
210 : }
211 10 : break;
212 0 : 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 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
218 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
219 0 : break;
220 0 : 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 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
225 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
226 0 : break;
227 0 : 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 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
231 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
232 0 : break;
233 2 : case MHD_HTTP_CONFLICT:
234 : /* Requested total refunds exceed deposited amount */
235 2 : rr.hr.ec = TALER_JSON_get_error_code (j);
236 2 : rr.hr.hint = TALER_JSON_get_error_hint (j);
237 2 : break;
238 2 : 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 2 : rr.hr.ec = TALER_JSON_get_error_code (j);
242 2 : rr.hr.hint = TALER_JSON_get_error_hint (j);
243 2 : break;
244 0 : case MHD_HTTP_FAILED_DEPENDENCY:
245 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
246 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
247 0 : break;
248 0 : 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 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
252 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
253 0 : break;
254 0 : default:
255 : /* unexpected response code */
256 0 : GNUNET_break_op (0);
257 0 : rr.hr.ec = TALER_JSON_get_error_code (j);
258 0 : rr.hr.hint = TALER_JSON_get_error_hint (j);
259 0 : 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 0 : break;
264 : }
265 14 : if (NULL != rh->cb)
266 : {
267 14 : rh->cb (rh->cb_cls,
268 : &rr);
269 14 : rh->cb = NULL;
270 : }
271 14 : TALER_EXCHANGE_post_coins_refund_cancel (rh);
272 14 : }
273 :
274 :
275 : struct TALER_EXCHANGE_PostCoinsRefundHandle *
276 14 : 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 14 : rh = GNUNET_new (struct TALER_EXCHANGE_PostCoinsRefundHandle);
289 14 : rh->ctx = ctx;
290 14 : rh->base_url = GNUNET_strdup (url);
291 14 : rh->keys = TALER_EXCHANGE_keys_incref (keys);
292 14 : rh->refund_amount = *amount;
293 14 : rh->h_contract_terms = *h_contract_terms;
294 14 : rh->coin_pub = *coin_pub;
295 14 : rh->rtransaction_id = rtransaction_id;
296 14 : rh->merchant_priv = *merchant_priv;
297 14 : GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
298 : &rh->merchant_pub.eddsa_pub);
299 14 : return rh;
300 : }
301 :
302 :
303 : enum TALER_ErrorCode
304 14 : 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 14 : rh->cb = cb;
315 14 : rh->cb_cls = cb_cls;
316 14 : TALER_merchant_refund_sign (&rh->coin_pub,
317 14 : &rh->h_contract_terms,
318 : rh->rtransaction_id,
319 14 : &rh->refund_amount,
320 14 : &rh->merchant_priv,
321 : &merchant_sig);
322 : {
323 : char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
324 : char *end;
325 :
326 14 : end = GNUNET_STRINGS_data_to_string (
327 14 : &rh->coin_pub,
328 : sizeof (struct TALER_CoinSpendPublicKeyP),
329 : pub_str,
330 : sizeof (pub_str));
331 14 : *end = '\0';
332 14 : GNUNET_snprintf (arg_str,
333 : sizeof (arg_str),
334 : "coins/%s/refund",
335 : pub_str);
336 : }
337 14 : 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 14 : rh->url = TALER_url_join (rh->base_url,
349 : arg_str,
350 : NULL);
351 14 : if (NULL == rh->url)
352 : {
353 0 : json_decref (refund_obj);
354 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
355 : }
356 14 : eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
357 28 : if ( (NULL == eh) ||
358 : (GNUNET_OK !=
359 14 : TALER_curl_easy_post (&rh->post_ctx,
360 : eh,
361 : refund_obj)) )
362 : {
363 0 : GNUNET_break (0);
364 0 : if (NULL != eh)
365 0 : curl_easy_cleanup (eh);
366 0 : json_decref (refund_obj);
367 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
368 : }
369 14 : json_decref (refund_obj);
370 14 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
371 : "URL for refund: `%s'\n",
372 : rh->url);
373 28 : rh->job = GNUNET_CURL_job_add2 (rh->ctx,
374 : eh,
375 14 : rh->post_ctx.headers,
376 : &handle_refund_finished,
377 : rh);
378 14 : if (NULL == rh->job)
379 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
380 14 : return TALER_EC_NONE;
381 : }
382 :
383 :
384 : void
385 14 : TALER_EXCHANGE_post_coins_refund_cancel (
386 : struct TALER_EXCHANGE_PostCoinsRefundHandle *rh)
387 : {
388 14 : if (NULL != rh->job)
389 : {
390 0 : GNUNET_CURL_job_cancel (rh->job);
391 0 : rh->job = NULL;
392 : }
393 14 : GNUNET_free (rh->url);
394 14 : GNUNET_free (rh->base_url);
395 14 : TALER_curl_easy_post_finished (&rh->post_ctx);
396 14 : TALER_EXCHANGE_keys_decref (rh->keys);
397 14 : GNUNET_free (rh);
398 14 : }
399 :
400 :
401 : /* end of exchange_api_post-coins-COIN_PUB-refund.c */
|