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