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