Line data Source code
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 2 : handle_refund_finished (void *cls,
86 : long response_code,
87 : const void *response)
88 : {
89 2 : struct TALER_MERCHANT_WalletOrderRefundHandle *orh = cls;
90 2 : const json_t *json = response;
91 2 : struct TALER_MERCHANT_WalletRefundResponse wrr = {
92 2 : .hr.http_status = (unsigned int) response_code,
93 : .hr.reply = json
94 : };
95 :
96 2 : orh->job = NULL;
97 2 : switch (response_code)
98 : {
99 0 : case 0:
100 0 : wrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
101 0 : break;
102 2 : case MHD_HTTP_OK:
103 : {
104 : const json_t *refunds;
105 : unsigned int refund_len;
106 : struct GNUNET_JSON_Specification spec[] = {
107 2 : TALER_JSON_spec_amount_any (
108 : "refund_amount",
109 : &wrr.details.ok.refund_amount),
110 2 : GNUNET_JSON_spec_array_const (
111 : "refunds",
112 : &refunds),
113 2 : GNUNET_JSON_spec_fixed_auto (
114 : "merchant_pub",
115 : &wrr.details.ok.merchant_pub),
116 2 : GNUNET_JSON_spec_end ()
117 : };
118 :
119 2 : if (GNUNET_OK !=
120 2 : GNUNET_JSON_parse (json,
121 : spec,
122 : NULL, NULL))
123 : {
124 0 : GNUNET_break_op (0);
125 0 : wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
126 0 : wrr.hr.http_status = 0;
127 0 : break;
128 : }
129 2 : refund_len = json_array_size (refunds);
130 2 : if ( (json_array_size (refunds) != (size_t) refund_len) ||
131 : (refund_len > MAX_REFUNDS) )
132 : {
133 0 : GNUNET_break (0);
134 0 : wrr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
135 0 : wrr.hr.http_status = 0;
136 0 : break;
137 : }
138 2 : {
139 2 : struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (refund_len)];
140 :
141 2 : memset (rds,
142 : 0,
143 : sizeof (rds));
144 6 : for (unsigned int i = 0; i<refund_len; i++)
145 : {
146 4 : struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
147 4 : const json_t *jrefund = json_array_get (refunds,
148 : i);
149 : const char *refund_status_type;
150 : uint32_t exchange_status;
151 4 : uint32_t eec = 0;
152 : struct GNUNET_JSON_Specification espec[] = {
153 4 : GNUNET_JSON_spec_string ("type",
154 : &refund_status_type),
155 4 : GNUNET_JSON_spec_uint32 ("exchange_status",
156 : &exchange_status),
157 4 : GNUNET_JSON_spec_uint64 ("rtransaction_id",
158 : &rd->rtransaction_id),
159 4 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
160 : &rd->coin_pub),
161 4 : TALER_JSON_spec_amount_any ("refund_amount",
162 : &rd->refund_amount),
163 4 : GNUNET_JSON_spec_mark_optional (
164 : GNUNET_JSON_spec_object_const ("exchange_reply",
165 : &rd->hr.reply),
166 : NULL),
167 4 : GNUNET_JSON_spec_mark_optional (
168 : GNUNET_JSON_spec_uint32 ("exchange_code",
169 : &eec),
170 : NULL),
171 4 : GNUNET_JSON_spec_end ()
172 : };
173 :
174 4 : if (GNUNET_OK !=
175 4 : GNUNET_JSON_parse (jrefund,
176 : espec,
177 : NULL, NULL))
178 : {
179 0 : GNUNET_break_op (0);
180 0 : wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
181 0 : wrr.hr.http_status = 0;
182 0 : goto finish;
183 : }
184 :
185 4 : rd->hr.http_status = exchange_status;
186 4 : rd->hr.ec = (enum TALER_ErrorCode) eec;
187 4 : switch (exchange_status)
188 : {
189 4 : case MHD_HTTP_OK:
190 : {
191 : struct GNUNET_JSON_Specification rspec[] = {
192 4 : GNUNET_JSON_spec_fixed_auto ("exchange_sig",
193 : &rd->details.ok.exchange_sig),
194 4 : GNUNET_JSON_spec_fixed_auto ("exchange_pub",
195 : &rd->details.ok.exchange_pub),
196 4 : GNUNET_JSON_spec_end ()
197 : };
198 :
199 4 : if (GNUNET_OK !=
200 4 : GNUNET_JSON_parse (jrefund,
201 : rspec,
202 : NULL,
203 : NULL))
204 : {
205 0 : GNUNET_break_op (0);
206 0 : wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
207 0 : wrr.hr.http_status = 0;
208 0 : goto finish;
209 : }
210 : /* check that type field is correct */
211 4 : if (0 != strcmp ("success",
212 : refund_status_type))
213 : {
214 0 : GNUNET_break_op (0);
215 0 : wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
216 0 : wrr.hr.http_status = 0;
217 0 : goto finish;
218 : }
219 : }
220 4 : break; /* end MHD_HTTP_OK */
221 0 : default:
222 0 : if (0 != strcmp ("failure",
223 : refund_status_type))
224 : {
225 0 : GNUNET_break_op (0);
226 0 : wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
227 0 : wrr.hr.http_status = 0;
228 0 : goto finish;
229 : }
230 : } /* switch on exchange status code */
231 : } /* for all refunds */
232 :
233 2 : wrr.details.ok.refunds = rds;
234 2 : wrr.details.ok.refunds_length = refund_len;
235 2 : orh->cb (orh->cb_cls,
236 : &wrr);
237 2 : TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
238 2 : return;
239 : } /* end 'rds' scope */
240 : } /* case MHD_HTTP_OK */
241 : break;
242 0 : case MHD_HTTP_NO_CONTENT:
243 0 : break;
244 0 : case MHD_HTTP_CONFLICT:
245 : case MHD_HTTP_NOT_FOUND:
246 0 : wrr.hr.ec = TALER_JSON_get_error_code (json);
247 0 : wrr.hr.hint = TALER_JSON_get_error_hint (json);
248 0 : break;
249 0 : default:
250 0 : GNUNET_break_op (0); /* unexpected status code */
251 0 : TALER_MERCHANT_parse_error_details_ (json,
252 : response_code,
253 : &wrr.hr);
254 0 : break;
255 : }
256 0 : finish:
257 0 : orh->cb (orh->cb_cls,
258 : &wrr);
259 0 : TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
260 : }
261 :
262 :
263 : struct TALER_MERCHANT_WalletOrderRefundHandle *
264 2 : 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 2 : orh = GNUNET_new (struct TALER_MERCHANT_WalletOrderRefundHandle);
277 2 : orh->ctx = ctx;
278 2 : orh->cb = cb;
279 2 : orh->cb_cls = cb_cls;
280 : {
281 : char *path;
282 :
283 2 : GNUNET_asprintf (&path,
284 : "orders/%s/refund",
285 : order_id);
286 2 : orh->url = TALER_url_join (backend_url,
287 : path,
288 : NULL);
289 2 : GNUNET_free (path);
290 : }
291 2 : if (NULL == orh->url)
292 : {
293 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
294 : "Could not construct request URL.\n");
295 0 : GNUNET_free (orh);
296 0 : return NULL;
297 : }
298 2 : req = GNUNET_JSON_PACK (
299 : GNUNET_JSON_pack_data_auto ("h_contract",
300 : h_contract_terms));
301 2 : eh = TALER_MERCHANT_curl_easy_get_ (orh->url);
302 2 : if (GNUNET_OK !=
303 2 : TALER_curl_easy_post (&orh->post_ctx,
304 : eh,
305 : req))
306 : {
307 0 : GNUNET_break (0);
308 0 : json_decref (req);
309 0 : curl_easy_cleanup (eh);
310 0 : GNUNET_free (orh->url);
311 0 : GNUNET_free (orh);
312 0 : return NULL;
313 : }
314 2 : json_decref (req);
315 4 : orh->job = GNUNET_CURL_job_add2 (ctx,
316 : eh,
317 2 : orh->post_ctx.headers,
318 : &handle_refund_finished,
319 : orh);
320 2 : if (NULL == orh->job)
321 : {
322 0 : GNUNET_free (orh->url);
323 0 : GNUNET_free (orh);
324 0 : return NULL;
325 : }
326 2 : return orh;
327 : }
328 :
329 :
330 : void
331 2 : TALER_MERCHANT_wallet_post_order_refund_cancel (
332 : struct TALER_MERCHANT_WalletOrderRefundHandle *orh)
333 : {
334 2 : if (NULL != orh->job)
335 : {
336 0 : GNUNET_CURL_job_cancel (orh->job);
337 0 : orh->job = NULL;
338 : }
339 2 : TALER_curl_easy_post_finished (&orh->post_ctx);
340 2 : GNUNET_free (orh->url);
341 2 : GNUNET_free (orh);
342 2 : }
343 :
344 :
345 : /* end of merchant_api_wallet_post_order_refund.c */
|