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 General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_refund.c
21 : * @brief Implement the /refund test command, plus other
22 : * corollary commands (?).
23 : * @author Marcello Stanisci
24 : */
25 : #include "platform.h"
26 : #include "taler_json_lib.h"
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler_testing_lib.h"
29 :
30 :
31 : /**
32 : * State for a "refund" CMD.
33 : */
34 : struct RefundState
35 : {
36 : /**
37 : * Expected HTTP response code.
38 : */
39 : unsigned int expected_response_code;
40 :
41 : /**
42 : * Amount to be refunded.
43 : */
44 : const char *refund_amount;
45 :
46 : /**
47 : * Reference to any command that can provide a coin to refund.
48 : */
49 : const char *coin_reference;
50 :
51 : /**
52 : * Refund transaction identifier.
53 : */
54 : uint64_t refund_transaction_id;
55 :
56 : /**
57 : * Entry in the coin's history generated by this operation.
58 : */
59 : struct TALER_EXCHANGE_CoinHistoryEntry che;
60 :
61 : /**
62 : * Public key of the refunded coin.
63 : */
64 : struct TALER_CoinSpendPublicKeyP coin;
65 :
66 : /**
67 : * Handle to the refund operation.
68 : */
69 : struct TALER_EXCHANGE_RefundHandle *rh;
70 :
71 : /**
72 : * Interpreter state.
73 : */
74 : struct TALER_TESTING_Interpreter *is;
75 : };
76 :
77 :
78 : /**
79 : * Check the result for the refund request, just check if the
80 : * response code is acceptable.
81 : *
82 : * @param cls closure
83 : * @param rr response details
84 : */
85 : static void
86 14 : refund_cb (void *cls,
87 : const struct TALER_EXCHANGE_RefundResponse *rr)
88 : {
89 14 : struct RefundState *rs = cls;
90 14 : const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
91 :
92 14 : rs->rh = NULL;
93 14 : if (rs->expected_response_code != hr->http_status)
94 : {
95 0 : TALER_TESTING_unexpected_status (rs->is,
96 : hr->http_status,
97 : rs->expected_response_code);
98 0 : return;
99 : }
100 14 : if (MHD_HTTP_OK == hr->http_status)
101 : {
102 : struct TALER_Amount refund_amount;
103 :
104 10 : if (GNUNET_OK !=
105 10 : TALER_string_to_amount (rs->refund_amount,
106 : &refund_amount))
107 : {
108 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
109 : "Failed to parse amount `%s'\n",
110 : rs->refund_amount);
111 0 : TALER_TESTING_interpreter_fail (rs->is);
112 0 : return;
113 : }
114 10 : if (0 >
115 10 : TALER_amount_subtract (&rs->che.amount,
116 : &refund_amount,
117 10 : &rs->che.details.refund.refund_fee))
118 : {
119 0 : GNUNET_break (0);
120 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
121 : "Failed to subtract %s from %s\n",
122 : TALER_amount2s (&rs->che.details.refund.refund_fee),
123 : rs->refund_amount);
124 0 : TALER_TESTING_interpreter_fail (rs->is);
125 0 : return;
126 : }
127 : }
128 14 : TALER_TESTING_interpreter_next (rs->is);
129 : }
130 :
131 :
132 : /**
133 : * Run the command.
134 : *
135 : * @param cls closure.
136 : * @param cmd the command to execute.
137 : * @param is the interpreter state.
138 : */
139 : static void
140 14 : refund_run (void *cls,
141 : const struct TALER_TESTING_Command *cmd,
142 : struct TALER_TESTING_Interpreter *is)
143 : {
144 14 : struct RefundState *rs = cls;
145 : const struct TALER_CoinSpendPrivateKeyP *coin_priv;
146 : const json_t *contract_terms;
147 : struct TALER_PrivateContractHashP h_contract_terms;
148 : struct TALER_Amount refund_amount;
149 : const struct TALER_MerchantPrivateKeyP *merchant_priv;
150 : const struct TALER_TESTING_Command *coin_cmd;
151 : const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
152 :
153 14 : rs->is = is;
154 14 : if (GNUNET_OK !=
155 14 : TALER_string_to_amount (rs->refund_amount,
156 : &refund_amount))
157 : {
158 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
159 : "Failed to parse amount `%s' at %s\n",
160 : rs->refund_amount,
161 : cmd->label);
162 0 : TALER_TESTING_interpreter_fail (is);
163 0 : return;
164 : }
165 14 : coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
166 : rs->coin_reference);
167 14 : if (NULL == coin_cmd)
168 : {
169 0 : GNUNET_break (0);
170 0 : TALER_TESTING_interpreter_fail (is);
171 0 : return;
172 : }
173 14 : if (GNUNET_OK !=
174 14 : TALER_TESTING_get_trait_contract_terms (coin_cmd,
175 : &contract_terms))
176 : {
177 0 : GNUNET_break (0);
178 0 : TALER_TESTING_interpreter_fail (is);
179 0 : return;
180 : }
181 14 : GNUNET_assert (GNUNET_OK ==
182 : TALER_JSON_contract_hash (contract_terms,
183 : &h_contract_terms));
184 :
185 : /* Hunting for a coin .. */
186 14 : if ( (GNUNET_OK !=
187 14 : TALER_TESTING_get_trait_coin_priv (coin_cmd,
188 : 0,
189 14 : &coin_priv)) ||
190 : (GNUNET_OK !=
191 14 : TALER_TESTING_get_trait_denom_pub (coin_cmd,
192 : 0,
193 : &denom_pub)) )
194 :
195 : {
196 0 : GNUNET_break (0);
197 0 : TALER_TESTING_interpreter_fail (is);
198 0 : return;
199 : }
200 :
201 14 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
202 : &rs->coin.eddsa_pub);
203 14 : if (GNUNET_OK !=
204 14 : TALER_TESTING_get_trait_merchant_priv (coin_cmd,
205 : &merchant_priv))
206 : {
207 0 : GNUNET_break (0);
208 0 : TALER_TESTING_interpreter_fail (is);
209 0 : return;
210 : }
211 14 : rs->che.type = TALER_EXCHANGE_CTT_REFUND;
212 14 : rs->che.details.refund.h_contract_terms = h_contract_terms;
213 14 : GNUNET_CRYPTO_eddsa_key_get_public (
214 14 : &merchant_priv->eddsa_priv,
215 : &rs->che.details.refund.merchant_pub.eddsa_pub);
216 14 : rs->che.details.refund.refund_fee = denom_pub->fees.refund;
217 14 : rs->che.details.refund.sig_amount = refund_amount;
218 14 : rs->che.details.refund.rtransaction_id = rs->refund_transaction_id;
219 14 : TALER_merchant_refund_sign (&rs->coin,
220 : &h_contract_terms,
221 : rs->refund_transaction_id,
222 : &refund_amount,
223 : merchant_priv,
224 : &rs->che.details.refund.sig);
225 14 : rs->rh = TALER_EXCHANGE_refund (
226 : TALER_TESTING_interpreter_get_context (is),
227 : TALER_TESTING_get_exchange_url (is),
228 : TALER_TESTING_get_keys (is),
229 : &refund_amount,
230 : &h_contract_terms,
231 14 : &rs->coin,
232 : rs->refund_transaction_id,
233 : merchant_priv,
234 : &refund_cb,
235 : rs);
236 14 : GNUNET_assert (NULL != rs->rh);
237 : }
238 :
239 :
240 : /**
241 : * Offer internal data from a "refund" CMD, to other commands.
242 : *
243 : * @param cls closure.
244 : * @param[out] ret result.
245 : * @param trait name of the trait.
246 : * @param index index number of the object to offer.
247 : * @return #GNUNET_OK on success.
248 : */
249 : static enum GNUNET_GenericReturnValue
250 36 : refund_traits (void *cls,
251 : const void **ret,
252 : const char *trait,
253 : unsigned int index)
254 : {
255 36 : struct RefundState *rs = cls;
256 : struct TALER_TESTING_Trait traits[] = {
257 36 : TALER_TESTING_make_trait_coin_history (0,
258 36 : &rs->che),
259 36 : TALER_TESTING_make_trait_coin_pub (0,
260 36 : &rs->coin),
261 36 : TALER_TESTING_trait_end ()
262 : };
263 :
264 36 : return TALER_TESTING_get_trait (traits,
265 : ret,
266 : trait,
267 : index);
268 : }
269 :
270 :
271 : /**
272 : * Free the state from a "refund" CMD, and possibly cancel
273 : * a pending operation thereof.
274 : *
275 : * @param cls closure.
276 : * @param cmd the command which is being cleaned up.
277 : */
278 : static void
279 14 : refund_cleanup (void *cls,
280 : const struct TALER_TESTING_Command *cmd)
281 : {
282 14 : struct RefundState *rs = cls;
283 :
284 14 : if (NULL != rs->rh)
285 : {
286 0 : TALER_TESTING_command_incomplete (rs->is,
287 : cmd->label);
288 0 : TALER_EXCHANGE_refund_cancel (rs->rh);
289 0 : rs->rh = NULL;
290 : }
291 14 : GNUNET_free (rs);
292 14 : }
293 :
294 :
295 : struct TALER_TESTING_Command
296 6 : TALER_TESTING_cmd_refund (const char *label,
297 : unsigned int expected_response_code,
298 : const char *refund_amount,
299 : const char *coin_reference)
300 : {
301 : struct RefundState *rs;
302 :
303 6 : rs = GNUNET_new (struct RefundState);
304 6 : rs->expected_response_code = expected_response_code;
305 6 : rs->refund_amount = refund_amount;
306 6 : rs->coin_reference = coin_reference;
307 : {
308 6 : struct TALER_TESTING_Command cmd = {
309 : .cls = rs,
310 : .label = label,
311 : .run = &refund_run,
312 : .cleanup = &refund_cleanup,
313 : .traits = &refund_traits
314 : };
315 :
316 6 : return cmd;
317 : }
318 : }
319 :
320 :
321 : struct TALER_TESTING_Command
322 8 : TALER_TESTING_cmd_refund_with_id (
323 : const char *label,
324 : unsigned int expected_response_code,
325 : const char *refund_amount,
326 : const char *coin_reference,
327 : uint64_t refund_transaction_id)
328 : {
329 : struct RefundState *rs;
330 :
331 8 : rs = GNUNET_new (struct RefundState);
332 8 : rs->expected_response_code = expected_response_code;
333 8 : rs->refund_amount = refund_amount;
334 8 : rs->coin_reference = coin_reference;
335 8 : rs->refund_transaction_id = refund_transaction_id;
336 : {
337 8 : struct TALER_TESTING_Command cmd = {
338 : .cls = rs,
339 : .label = label,
340 : .run = &refund_run,
341 : .cleanup = &refund_cleanup,
342 : .traits = &refund_traits
343 : };
344 :
345 8 : return cmd;
346 : }
347 : }
|