Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2023 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_order_abort.c
21 : * @brief Implementation of the POST /orders/$ID/abort request
22 : * of the merchant's HTTP API
23 : * @author Christian Grothoff
24 : * @author Marcello Stanisci
25 : */
26 : #include "platform.h"
27 : #include <curl/curl.h>
28 : #include <jansson.h>
29 : #include <microhttpd.h> /* just for HTTP status codes */
30 : #include <gnunet/gnunet_util_lib.h>
31 : #include <gnunet/gnunet_curl_lib.h>
32 : #include "taler_merchant_service.h"
33 : #include "merchant_api_curl_defaults.h"
34 : #include "merchant_api_common.h"
35 : #include <taler/taler_json_lib.h>
36 : #include <taler/taler_signatures.h>
37 : #include <taler/taler_exchange_service.h>
38 : #include <taler/taler_curl_lib.h>
39 :
40 :
41 : /**
42 : * Maximum number of refunds we return.
43 : */
44 : #define MAX_REFUNDS 1024
45 :
46 :
47 : /**
48 : * @brief An abort Handle
49 : */
50 : struct TALER_MERCHANT_OrderAbortHandle
51 : {
52 : /**
53 : * Hash of the contract.
54 : */
55 : struct TALER_PrivateContractHashP h_contract_terms;
56 :
57 : /**
58 : * Public key of the merchant.
59 : */
60 : struct TALER_MerchantPublicKeyP merchant_pub;
61 :
62 : /**
63 : * The url for this request.
64 : */
65 : char *url;
66 :
67 : /**
68 : * Handle for the request.
69 : */
70 : struct GNUNET_CURL_Job *job;
71 :
72 : /**
73 : * Function to call with the result.
74 : */
75 : TALER_MERCHANT_AbortCallback abort_cb;
76 :
77 : /**
78 : * Closure for @a abort_cb.
79 : */
80 : void *abort_cb_cls;
81 :
82 : /**
83 : * Reference to the execution context.
84 : */
85 : struct GNUNET_CURL_Context *ctx;
86 :
87 : /**
88 : * Minor context that holds body and headers.
89 : */
90 : struct TALER_CURL_PostContext post_ctx;
91 :
92 : /**
93 : * The coins we are aborting on.
94 : */
95 : struct TALER_MERCHANT_AbortCoin *coins;
96 :
97 : /**
98 : * Number of @e coins we are paying with.
99 : */
100 : unsigned int num_coins;
101 :
102 : };
103 :
104 :
105 : /**
106 : * Check that the response for an abort is well-formed,
107 : * and call the application callback with the result if it is
108 : * OK. Otherwise returns #GNUNET_SYSERR.
109 : *
110 : * @param oah handle to operation that created the reply
111 : * @param[in] ar abort response, partially initialized
112 : * @param json the reply to parse
113 : * @return #GNUNET_OK on success
114 : */
115 : static enum GNUNET_GenericReturnValue
116 2 : check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
117 : struct TALER_MERCHANT_AbortResponse *ar,
118 : const json_t *json)
119 : {
120 : const json_t *refunds;
121 : unsigned int num_refunds;
122 : struct GNUNET_JSON_Specification spec[] = {
123 2 : GNUNET_JSON_spec_array_const ("refunds",
124 : &refunds),
125 2 : GNUNET_JSON_spec_end ()
126 : };
127 :
128 2 : if (GNUNET_OK !=
129 2 : GNUNET_JSON_parse (json,
130 : spec,
131 : NULL, NULL))
132 : {
133 0 : GNUNET_break_op (0);
134 0 : return GNUNET_SYSERR;
135 : }
136 2 : num_refunds = (unsigned int) json_array_size (refunds);
137 2 : if ( (json_array_size (refunds) != (size_t) num_refunds) ||
138 : (num_refunds > MAX_REFUNDS) )
139 : {
140 0 : GNUNET_break (0);
141 0 : return GNUNET_SYSERR;
142 : }
143 :
144 2 : {
145 2 : struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)];
146 :
147 4 : for (unsigned int i = 0; i<num_refunds; i++)
148 : {
149 2 : json_t *refund = json_array_get (refunds, i);
150 : uint32_t exchange_status;
151 : struct GNUNET_JSON_Specification spec_es[] = {
152 2 : GNUNET_JSON_spec_uint32 ("exchange_status",
153 : &exchange_status),
154 2 : GNUNET_JSON_spec_end ()
155 : };
156 :
157 2 : if (GNUNET_OK !=
158 2 : GNUNET_JSON_parse (refund,
159 : spec_es,
160 : NULL, NULL))
161 : {
162 0 : GNUNET_break_op (0);
163 0 : return GNUNET_SYSERR;
164 : }
165 2 : if (MHD_HTTP_OK == exchange_status)
166 : {
167 : struct GNUNET_JSON_Specification spec_detail[] = {
168 2 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
169 : &res[i].exchange_sig),
170 2 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
171 : &res[i].exchange_pub),
172 2 : GNUNET_JSON_spec_end ()
173 : };
174 :
175 2 : if (GNUNET_OK !=
176 2 : GNUNET_JSON_parse (refund,
177 : spec_detail,
178 : NULL, NULL))
179 : {
180 0 : GNUNET_break_op (0);
181 0 : return GNUNET_SYSERR;
182 : }
183 :
184 2 : if (GNUNET_OK !=
185 2 : TALER_exchange_online_refund_confirmation_verify (
186 2 : &oah->h_contract_terms,
187 2 : &oah->coins[i].coin_pub,
188 2 : &oah->merchant_pub,
189 : 0, /* transaction id */
190 2 : &oah->coins[i].amount_with_fee,
191 2 : &res[i].exchange_pub,
192 2 : &res[i].exchange_sig))
193 : {
194 0 : GNUNET_break_op (0);
195 0 : return GNUNET_SYSERR;
196 : }
197 : }
198 : }
199 2 : ar->details.ok.merchant_pub = &oah->merchant_pub;
200 2 : ar->details.ok.num_aborts = num_refunds;
201 2 : ar->details.ok.aborts = res;
202 2 : oah->abort_cb (oah->abort_cb_cls,
203 : ar);
204 2 : oah->abort_cb = NULL;
205 : }
206 2 : return GNUNET_OK;
207 : }
208 :
209 :
210 : /**
211 : * Function called when we're done processing the
212 : * abort request.
213 : *
214 : * @param cls the `struct TALER_MERCHANT_OrderAbortHandle`
215 : * @param response_code HTTP response code, 0 on error
216 : * @param response response body, NULL if not in JSON
217 : */
218 : static void
219 2 : handle_abort_finished (void *cls,
220 : long response_code,
221 : const void *response)
222 : {
223 2 : struct TALER_MERCHANT_OrderAbortHandle *oah = cls;
224 2 : const json_t *json = response;
225 2 : struct TALER_MERCHANT_AbortResponse ar = {
226 2 : .hr.http_status = (unsigned int) response_code,
227 : .hr.reply = json
228 : };
229 :
230 2 : oah->job = NULL;
231 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
232 : "/pay completed with response code %u\n",
233 : (unsigned int) response_code);
234 2 : switch (response_code)
235 : {
236 0 : case 0:
237 0 : ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
238 0 : break;
239 2 : case MHD_HTTP_OK:
240 2 : if (GNUNET_OK ==
241 2 : check_abort_refund (oah,
242 : &ar,
243 : json))
244 : {
245 2 : TALER_MERCHANT_order_abort_cancel (oah);
246 2 : return;
247 : }
248 0 : ar.hr.http_status = 0;
249 0 : ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
250 0 : break;
251 0 : case MHD_HTTP_BAD_REQUEST:
252 0 : ar.hr.ec = TALER_JSON_get_error_code (json);
253 0 : ar.hr.hint = TALER_JSON_get_error_hint (json);
254 : /* This should never happen, either us or the
255 : merchant is buggy (or API version conflict); just
256 : pass JSON reply to the application */
257 0 : break;
258 0 : case MHD_HTTP_FORBIDDEN:
259 0 : ar.hr.ec = TALER_JSON_get_error_code (json);
260 0 : ar.hr.hint = TALER_JSON_get_error_hint (json);
261 0 : break;
262 0 : case MHD_HTTP_NOT_FOUND:
263 0 : ar.hr.ec = TALER_JSON_get_error_code (json);
264 0 : ar.hr.hint = TALER_JSON_get_error_hint (json);
265 : /* Nothing really to verify, this should never
266 : happen, we should pass the JSON reply to the
267 : application */
268 0 : break;
269 0 : case MHD_HTTP_REQUEST_TIMEOUT:
270 0 : ar.hr.ec = TALER_JSON_get_error_code (json);
271 0 : ar.hr.hint = TALER_JSON_get_error_hint (json);
272 : /* Nothing really to verify, merchant says one of
273 : the signatures is invalid; as we checked them,
274 : this should never happen, we should pass the JSON
275 : reply to the application */
276 0 : break;
277 0 : case MHD_HTTP_PRECONDITION_FAILED:
278 : /* Our *payment* already succeeded fully. */
279 0 : ar.hr.ec = TALER_JSON_get_error_code (json);
280 0 : ar.hr.hint = TALER_JSON_get_error_hint (json);
281 0 : break;
282 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
283 0 : ar.hr.ec = TALER_JSON_get_error_code (json);
284 0 : ar.hr.hint = TALER_JSON_get_error_hint (json);
285 : /* Server had an internal issue; we should retry,
286 : but this API leaves this to the application */
287 0 : break;
288 0 : case MHD_HTTP_BAD_GATEWAY:
289 0 : TALER_MERCHANT_parse_error_details_ (json,
290 : response_code,
291 : &ar.hr);
292 : /* Nothing really to verify, the merchant is blaming the exchange.
293 : We should pass the JSON reply to the application */
294 0 : break;
295 0 : default:
296 : /* unexpected response code */
297 0 : TALER_MERCHANT_parse_error_details_ (json,
298 : response_code,
299 : &ar.hr);
300 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
301 : "Unexpected response code %u/%d\n",
302 : (unsigned int) response_code,
303 : (int) ar.hr.ec);
304 0 : GNUNET_break_op (0);
305 0 : break;
306 : }
307 0 : oah->abort_cb (oah->abort_cb_cls,
308 : &ar);
309 0 : TALER_MERCHANT_order_abort_cancel (oah);
310 : }
311 :
312 :
313 : struct TALER_MERCHANT_OrderAbortHandle *
314 2 : TALER_MERCHANT_order_abort (
315 : struct GNUNET_CURL_Context *ctx,
316 : const char *merchant_url,
317 : const char *order_id,
318 : const struct TALER_MerchantPublicKeyP *merchant_pub,
319 : const struct TALER_PrivateContractHashP *h_contract,
320 : unsigned int num_coins,
321 : const struct TALER_MERCHANT_AbortCoin coins[static num_coins],
322 : TALER_MERCHANT_AbortCallback cb,
323 : void *cb_cls)
324 2 : {
325 : struct TALER_MERCHANT_OrderAbortHandle *oah;
326 : json_t *abort_obj;
327 : json_t *j_coins;
328 :
329 2 : j_coins = json_array ();
330 2 : if (NULL == j_coins)
331 : {
332 0 : GNUNET_break (0);
333 0 : return NULL;
334 : }
335 4 : for (unsigned int i = 0; i<num_coins; i++)
336 : {
337 2 : const struct TALER_MERCHANT_AbortCoin *ac = &coins[i];
338 : json_t *j_coin;
339 :
340 : /* create JSON for this coin */
341 2 : j_coin = GNUNET_JSON_PACK (
342 : GNUNET_JSON_pack_data_auto ("coin_pub",
343 : &ac->coin_pub),
344 : /* FIXME: no longer needed since **v18**, remove eventually! */
345 : TALER_JSON_pack_amount ("contribution",
346 : &ac->amount_with_fee),
347 : GNUNET_JSON_pack_string ("exchange_url",
348 : ac->exchange_url));
349 2 : if (0 !=
350 2 : json_array_append_new (j_coins,
351 : j_coin))
352 : {
353 0 : GNUNET_break (0);
354 0 : json_decref (j_coins);
355 0 : return NULL;
356 : }
357 : }
358 2 : abort_obj = GNUNET_JSON_PACK (
359 : GNUNET_JSON_pack_array_steal ("coins",
360 : j_coins),
361 : GNUNET_JSON_pack_data_auto ("h_contract",
362 : h_contract));
363 2 : oah = GNUNET_new (struct TALER_MERCHANT_OrderAbortHandle);
364 2 : oah->h_contract_terms = *h_contract;
365 2 : oah->merchant_pub = *merchant_pub;
366 2 : oah->ctx = ctx;
367 2 : oah->abort_cb = cb;
368 2 : oah->abort_cb_cls = cb_cls;
369 : {
370 : char *path;
371 :
372 2 : GNUNET_asprintf (&path,
373 : "orders/%s/abort",
374 : order_id);
375 2 : oah->url = TALER_url_join (merchant_url,
376 : path,
377 : NULL);
378 2 : GNUNET_free (path);
379 : }
380 2 : if (NULL == oah->url)
381 : {
382 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
383 : "Could not construct request URL.\n");
384 0 : json_decref (abort_obj);
385 0 : GNUNET_free (oah);
386 0 : return NULL;
387 : }
388 2 : oah->num_coins = num_coins;
389 2 : oah->coins = GNUNET_new_array (num_coins,
390 : struct TALER_MERCHANT_AbortCoin);
391 2 : GNUNET_memcpy (oah->coins,
392 : coins,
393 : num_coins * sizeof (struct TALER_MERCHANT_AbortCoin));
394 : {
395 : CURL *eh;
396 :
397 2 : eh = TALER_MERCHANT_curl_easy_get_ (oah->url);
398 2 : if (GNUNET_OK !=
399 2 : TALER_curl_easy_post (&oah->post_ctx,
400 : eh,
401 : abort_obj))
402 : {
403 0 : GNUNET_break (0);
404 0 : curl_easy_cleanup (eh);
405 0 : json_decref (abort_obj);
406 0 : GNUNET_free (oah);
407 0 : return NULL;
408 : }
409 2 : json_decref (abort_obj);
410 4 : oah->job = GNUNET_CURL_job_add2 (ctx,
411 : eh,
412 2 : oah->post_ctx.headers,
413 : &handle_abort_finished,
414 : oah);
415 : }
416 2 : return oah;
417 : }
418 :
419 :
420 : void
421 2 : TALER_MERCHANT_order_abort_cancel (
422 : struct TALER_MERCHANT_OrderAbortHandle *oah)
423 : {
424 2 : if (NULL != oah->job)
425 : {
426 0 : GNUNET_CURL_job_cancel (oah->job);
427 0 : oah->job = NULL;
428 : }
429 2 : TALER_curl_easy_post_finished (&oah->post_ctx);
430 2 : GNUNET_free (oah->coins);
431 2 : GNUNET_free (oah->url);
432 2 : GNUNET_free (oah);
433 2 : }
434 :
435 :
436 : /* end of merchant_api_post_order_abort.c */
|