Line data Source code
1 : /*
2 : This file is part of GNU Taler
3 : Copyright (C) 2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, 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 General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file util/contract_serialize.c
18 : * @brief shared logic for contract terms serialization
19 : * @author Iván Ávalos
20 : */
21 :
22 : #include "platform.h"
23 : #include <gnunet/gnunet_json_lib.h>
24 : #include <gnunet/gnunet_common.h>
25 : #include <taler/taler_json_lib.h>
26 : #include <jansson.h>
27 : #include "taler/taler_util.h"
28 : #include "taler_merchant_util.h"
29 :
30 : /**
31 : * Get JSON representation of merchant details.
32 : *
33 : * @param[in] contract contract terms
34 : * @return JSON object with merchant details; NULL on error
35 : */
36 : static json_t *
37 2 : json_from_merchant_details (
38 : const struct TALER_MERCHANT_Contract *contract)
39 : {
40 2 : return GNUNET_JSON_PACK (
41 : GNUNET_JSON_pack_string ("name",
42 : contract->merchant.name),
43 : GNUNET_JSON_pack_allow_null (
44 : GNUNET_JSON_pack_string ("email",
45 : contract->merchant.email)),
46 : GNUNET_JSON_pack_allow_null (
47 : GNUNET_JSON_pack_string ("website",
48 : contract->merchant.website)),
49 : GNUNET_JSON_pack_allow_null (
50 : GNUNET_JSON_pack_string ("logo",
51 : contract->merchant.logo)),
52 : GNUNET_JSON_pack_allow_null (
53 : GNUNET_JSON_pack_object_steal ("address",
54 : contract->merchant.address)),
55 : GNUNET_JSON_pack_allow_null (
56 : GNUNET_JSON_pack_object_steal ("jurisdiction",
57 : contract->merchant.jurisdiction)));
58 : }
59 :
60 :
61 : /**
62 : * Get JSON representation of contract choice input.
63 : *
64 : * @param[in] input contract terms choice input
65 : * @return JSON representation of @a input; NULL on error
66 : */
67 : static json_t *
68 6 : json_from_contract_input (
69 : const struct TALER_MERCHANT_ContractInput *input)
70 : {
71 6 : switch (input->type)
72 : {
73 0 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
74 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
75 : "invalid contract input type");
76 0 : GNUNET_assert (0);
77 : return NULL;
78 6 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
79 6 : return GNUNET_JSON_PACK (
80 : GNUNET_JSON_pack_string ("type",
81 : "token"),
82 : GNUNET_JSON_pack_string ("token_family_slug",
83 : input->details.token.token_family_slug),
84 : GNUNET_JSON_pack_int64 ("count",
85 : input->details.token.count));
86 : }
87 :
88 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
89 : "unsupported contract input type %d",
90 : input->type);
91 0 : GNUNET_assert (0);
92 : return NULL;
93 : }
94 :
95 :
96 : /**
97 : * Get JSON representation of contract choice output.
98 : *
99 : * @param[in] output contract terms choice output
100 : * @return JSON representation of @a output; NULL on error
101 : */
102 : static json_t *
103 10 : json_from_contract_output (
104 : const struct TALER_MERCHANT_ContractOutput *output)
105 : {
106 10 : switch (output->type)
107 : {
108 0 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
109 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
110 : "invalid contract output type");
111 0 : GNUNET_assert (0);
112 : return NULL;
113 9 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
114 9 : return GNUNET_JSON_PACK (
115 : GNUNET_JSON_pack_string ("type",
116 : "token"),
117 : GNUNET_JSON_pack_string ("token_family_slug",
118 : output->details.token.token_family_slug),
119 : GNUNET_JSON_pack_uint64 ("count",
120 : output->details.token.count),
121 : GNUNET_JSON_pack_uint64 ("key_index",
122 : output->details.token.key_index));
123 1 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
124 : {
125 : json_t *donau_urls;
126 :
127 1 : donau_urls = json_array ();
128 1 : GNUNET_assert (NULL != donau_urls);
129 :
130 1 : for (unsigned i = 0;
131 4 : i < output->details.donation_receipt.donau_urls_len;
132 3 : i++)
133 3 : GNUNET_assert (0 ==
134 : json_array_append (donau_urls,
135 : json_string (output->details.
136 : donation_receipt.
137 : donau_urls[i])));
138 :
139 1 : return GNUNET_JSON_PACK (
140 : GNUNET_JSON_pack_string ("type",
141 : "tax-receipt"),
142 : GNUNET_JSON_pack_array_steal ("donau_urls",
143 : donau_urls),
144 : GNUNET_JSON_pack_allow_null (
145 : TALER_JSON_pack_amount ("amount",
146 : &output->details.donation_receipt.amount)));
147 : }
148 : }
149 :
150 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
151 : "unsupported contract output type %d",
152 : output->type);
153 0 : GNUNET_assert (0);
154 : return NULL;
155 : }
156 :
157 :
158 : json_t *
159 11 : TALER_MERCHANT_json_from_contract_choice (
160 : const struct TALER_MERCHANT_ContractChoice *choice,
161 : bool order)
162 : {
163 : json_t *inputs;
164 : json_t *outputs;
165 :
166 11 : inputs = json_array ();
167 11 : GNUNET_assert (NULL != inputs);
168 17 : for (unsigned int i = 0; i < choice->inputs_len; i++)
169 6 : GNUNET_assert (0 ==
170 : json_array_append_new (inputs,
171 : json_from_contract_input (
172 : &choice->inputs[i])));
173 :
174 11 : outputs = json_array ();
175 11 : GNUNET_assert (NULL != outputs);
176 21 : for (unsigned int i = 0; i < choice->outputs_len; i++)
177 10 : GNUNET_assert (0 ==
178 : json_array_append_new (outputs,
179 : json_from_contract_output (
180 : &choice->outputs[i])));
181 :
182 11 : return GNUNET_JSON_PACK (
183 : TALER_JSON_pack_amount ("amount",
184 : &choice->amount),
185 : (order)
186 : ? GNUNET_JSON_pack_allow_null (
187 : TALER_JSON_pack_amount ("max_fee",
188 : /* workaround for nullable amount */
189 : (GNUNET_OK ==
190 : TALER_amount_is_valid (&choice->max_fee))
191 : ? &choice->max_fee
192 : : NULL))
193 : : TALER_JSON_pack_amount ("max_fee",
194 : &choice->max_fee),
195 : (order)
196 : ? GNUNET_JSON_pack_allow_null (
197 : GNUNET_JSON_pack_array_steal ("inputs",
198 : inputs))
199 : : GNUNET_JSON_pack_array_steal ("inputs",
200 : inputs),
201 : (order)
202 : ? GNUNET_JSON_pack_allow_null (
203 : GNUNET_JSON_pack_array_steal ("outputs",
204 : outputs))
205 : : GNUNET_JSON_pack_array_steal ("outputs",
206 : outputs));
207 : }
208 :
209 :
210 : /**
211 : * Get JSON representation of contract token family key.
212 : *
213 : * @param[in] key contract token family key
214 : * @return JSON representation of @a key; NULL on error
215 : */
216 : static json_t *
217 10 : json_from_token_family_key (
218 : const struct TALER_MERCHANT_ContractTokenFamilyKey *key)
219 : {
220 10 : return GNUNET_JSON_PACK (
221 : GNUNET_JSON_pack_timestamp ("signature_validity_start",
222 : key->valid_after),
223 : GNUNET_JSON_pack_timestamp ("signature_validity_end",
224 : key->valid_before),
225 : TALER_JSON_pack_token_pub (NULL,
226 : &key->pub));
227 : }
228 :
229 :
230 : /**
231 : * Get JSON representation of contract token family details.
232 : *
233 : * @param[in] family contract token family
234 : * @return JSON representation of @a family->details; NULL on error
235 : */
236 : static json_t *
237 11 : json_from_token_family_details (
238 : const struct TALER_MERCHANT_ContractTokenFamily *family)
239 : {
240 11 : switch (family->kind)
241 : {
242 0 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
243 0 : break;
244 9 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
245 : {
246 : json_t *trusted_domains;
247 :
248 9 : trusted_domains = json_array ();
249 9 : GNUNET_assert (NULL != trusted_domains);
250 9 : for (unsigned int i = 0;
251 12 : i < family->details.subscription.trusted_domains_len;
252 3 : i++)
253 3 : GNUNET_assert (0 ==
254 : json_array_append_new (
255 : trusted_domains,
256 : json_string (
257 : family->details.subscription.trusted_domains[i])));
258 :
259 9 : return GNUNET_JSON_PACK (
260 : GNUNET_JSON_pack_string ("class",
261 : "subscription"),
262 : GNUNET_JSON_pack_array_steal ("trusted_domains",
263 : trusted_domains));
264 : }
265 2 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
266 : {
267 : json_t *expected_domains;
268 :
269 2 : expected_domains = json_array ();
270 2 : GNUNET_assert (NULL != expected_domains);
271 2 : for (unsigned int i = 0;
272 5 : i < family->details.discount.expected_domains_len;
273 3 : i++)
274 3 : GNUNET_assert (0 ==
275 : json_array_append_new (
276 : expected_domains,
277 : json_string (
278 : family->details.discount.expected_domains[i])));
279 :
280 2 : return GNUNET_JSON_PACK (
281 : GNUNET_JSON_pack_string ("class",
282 : "discount"),
283 : GNUNET_JSON_pack_array_steal ("expected_domains",
284 : expected_domains));
285 : }
286 : }
287 :
288 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
289 : "unsupported token family kind %d",
290 : family->kind);
291 0 : GNUNET_assert (0);
292 : return NULL;
293 : }
294 :
295 :
296 : json_t *
297 11 : TALER_MERCHANT_json_from_token_family (
298 : const struct TALER_MERCHANT_ContractTokenFamily *family)
299 : {
300 : json_t *keys;
301 :
302 11 : keys = json_array ();
303 11 : GNUNET_assert (NULL != keys);
304 21 : for (unsigned int i = 0; i < family->keys_len; i++)
305 10 : GNUNET_assert (0 == json_array_append_new (
306 : keys,
307 : json_from_token_family_key (
308 : &family->keys[i])));
309 :
310 11 : return GNUNET_JSON_PACK (
311 : GNUNET_JSON_pack_string ("name",
312 : family->name),
313 : GNUNET_JSON_pack_string ("description",
314 : family->description),
315 : GNUNET_JSON_pack_object_steal ("description_i18n",
316 : family->description_i18n),
317 : GNUNET_JSON_pack_array_steal ("keys",
318 : keys),
319 : GNUNET_JSON_pack_object_steal ("details",
320 : json_from_token_family_details (family)),
321 : GNUNET_JSON_pack_bool ("critical",
322 : family->critical));
323 : }
324 :
325 :
326 : /**
327 : * Get JSON object with contract terms v0-specific fields.
328 : *
329 : * @param[in] input contract terms v0
330 : * @return JSON object with @a input v0 fields; NULL on error
331 : */
332 : static json_t *
333 1 : json_from_contract_v0 (
334 : const struct TALER_MERCHANT_Contract *input)
335 : {
336 1 : return GNUNET_JSON_PACK (
337 : TALER_JSON_pack_amount ("amount",
338 : &input->details.v0.brutto),
339 : TALER_JSON_pack_amount ("max_fee",
340 : &input->details.v0.max_fee));
341 : }
342 :
343 :
344 : /**
345 : * Get JSON object with contract terms v1-specific fields.
346 : *
347 : * @param[in] input contract terms v1
348 : * @return JSON object with @a input v1 fields; NULL on error
349 : */
350 : static json_t *
351 1 : json_from_contract_v1 (
352 : const struct TALER_MERCHANT_Contract *input)
353 : {
354 : json_t *choices;
355 : json_t *families;
356 :
357 1 : choices = json_array ();
358 1 : GNUNET_assert (0 != choices);
359 2 : for (unsigned i = 0; i < input->details.v1.choices_len; i++)
360 1 : GNUNET_assert (0 == json_array_append_new (
361 : choices,
362 : TALER_MERCHANT_json_from_contract_choice (
363 : &input->details.v1.choices[i],
364 : false)));
365 :
366 1 : families = json_object ();
367 1 : GNUNET_assert (0 != families);
368 3 : for (unsigned i = 0; i < input->details.v1.token_authorities_len; i++)
369 2 : GNUNET_assert (0 == json_object_set_new (
370 : families,
371 : input->details.v1.token_authorities[i].slug,
372 : TALER_MERCHANT_json_from_token_family (
373 : &input->details.v1.token_authorities[i])));
374 :
375 1 : return GNUNET_JSON_PACK (
376 : GNUNET_JSON_pack_array_steal ("choices",
377 : choices),
378 : GNUNET_JSON_pack_object_steal ("token_families",
379 : families));
380 : }
381 :
382 :
383 : json_t *
384 2 : TALER_MERCHANT_contract_serialize (
385 : const struct TALER_MERCHANT_Contract *input,
386 : bool nonce_optional)
387 : {
388 : json_t *details;
389 :
390 2 : switch (input->version)
391 : {
392 1 : case TALER_MERCHANT_CONTRACT_VERSION_0:
393 1 : details = json_from_contract_v0 (input);
394 1 : goto success;
395 1 : case TALER_MERCHANT_CONTRACT_VERSION_1:
396 1 : details = json_from_contract_v1 (input);
397 1 : goto success;
398 : }
399 :
400 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
401 : "unknown contract type version %d",
402 : input->version);
403 0 : GNUNET_assert (0);
404 : return NULL;
405 :
406 2 : success:
407 2 : return GNUNET_JSON_PACK (
408 : GNUNET_JSON_pack_uint64 ("version",
409 : input->version),
410 : GNUNET_JSON_pack_string ("summary",
411 : input->summary),
412 : GNUNET_JSON_pack_allow_null (
413 : GNUNET_JSON_pack_object_steal ("summary_i18n",
414 : input->summary_i18n)),
415 : GNUNET_JSON_pack_string ("order_id",
416 : input->order_id),
417 : GNUNET_JSON_pack_allow_null (
418 : GNUNET_JSON_pack_string ("public_reorder_url",
419 : input->public_reorder_url)),
420 : GNUNET_JSON_pack_allow_null (
421 : GNUNET_JSON_pack_string ("fulfillment_url",
422 : input->fulfillment_url)),
423 : GNUNET_JSON_pack_allow_null (
424 : GNUNET_JSON_pack_string ("fulfillment_message",
425 : input->fulfillment_message)),
426 : GNUNET_JSON_pack_allow_null (
427 : GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
428 : input->fulfillment_message_i18n)),
429 : GNUNET_JSON_pack_array_steal ("products",
430 : input->products),
431 : GNUNET_JSON_pack_timestamp ("timestamp",
432 : input->timestamp),
433 : GNUNET_JSON_pack_timestamp ("refund_deadline",
434 : input->refund_deadline),
435 : GNUNET_JSON_pack_timestamp ("pay_deadline",
436 : input->pay_deadline),
437 : GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
438 : input->wire_deadline),
439 : GNUNET_JSON_pack_data_auto ("merchant_pub",
440 : &input->merchant_pub.eddsa_pub),
441 : GNUNET_JSON_pack_string ("merchant_base_url",
442 : input->merchant_base_url),
443 : GNUNET_JSON_pack_object_steal ("merchant",
444 : json_from_merchant_details (input)),
445 : GNUNET_JSON_pack_data_auto ("h_wire",
446 : &input->h_wire),
447 : GNUNET_JSON_pack_string ("wire_method",
448 : input->wire_method),
449 : GNUNET_JSON_pack_array_steal ("exchanges",
450 : input->exchanges),
451 : GNUNET_JSON_pack_allow_null (
452 : GNUNET_JSON_pack_object_steal ("delivery_location",
453 : input->delivery_location)),
454 : GNUNET_JSON_pack_allow_null (
455 : GNUNET_JSON_pack_timestamp ("delivery_date",
456 : input->delivery_date)),
457 : (nonce_optional)
458 : ? GNUNET_JSON_pack_allow_null (
459 : GNUNET_JSON_pack_string ("nonce",
460 : input->nonce))
461 : : GNUNET_JSON_pack_string ("nonce",
462 : input->nonce),
463 : GNUNET_JSON_pack_allow_null (
464 : GNUNET_JSON_pack_time_rel ("auto_refund",
465 : input->auto_refund)),
466 : GNUNET_JSON_pack_allow_null (
467 : GNUNET_JSON_pack_object_steal ("extra",
468 : input->extra)),
469 : GNUNET_JSON_pack_allow_null (
470 : GNUNET_JSON_pack_uint64 ("minimum_age",
471 : input->minimum_age)),
472 : GNUNET_JSON_pack_object_steal (NULL,
473 : details));
474 : }
|