Line data Source code
1 : /*
2 : This file is part of GNU Taler
3 : Copyright (C) 2024, 2025 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 : * @author Christian Grothoff
21 : */
22 :
23 : #include "platform.h"
24 : #include <gnunet/gnunet_json_lib.h>
25 : #include <gnunet/gnunet_common.h>
26 : #include <taler/taler_json_lib.h>
27 : #include <jansson.h>
28 : #include "taler/taler_util.h"
29 : #include "taler_merchant_util.h"
30 :
31 : /**
32 : * Get JSON representation of merchant details.
33 : *
34 : * @param[in] contract contract terms
35 : * @return JSON object with merchant details; NULL on error
36 : */
37 : static json_t *
38 2 : json_from_merchant_details (
39 : const struct TALER_MERCHANT_Contract *contract)
40 : {
41 2 : return GNUNET_JSON_PACK (
42 : GNUNET_JSON_pack_string ("name",
43 : contract->merchant.name),
44 : GNUNET_JSON_pack_allow_null (
45 : GNUNET_JSON_pack_string ("email",
46 : contract->merchant.email)),
47 : GNUNET_JSON_pack_allow_null (
48 : GNUNET_JSON_pack_string ("website",
49 : contract->merchant.website)),
50 : GNUNET_JSON_pack_allow_null (
51 : GNUNET_JSON_pack_string ("logo",
52 : contract->merchant.logo)),
53 : GNUNET_JSON_pack_allow_null (
54 : GNUNET_JSON_pack_object_steal ("address",
55 : contract->merchant.address)),
56 : GNUNET_JSON_pack_allow_null (
57 : GNUNET_JSON_pack_object_steal ("jurisdiction",
58 : contract->merchant.jurisdiction)));
59 : }
60 :
61 :
62 : /**
63 : * Get JSON representation of contract choice input.
64 : *
65 : * @param[in] input contract terms choice input
66 : * @return JSON representation of @a input; NULL on error
67 : */
68 : static json_t *
69 6 : json_from_contract_input (
70 : const struct TALER_MERCHANT_ContractInput *input)
71 : {
72 6 : switch (input->type)
73 : {
74 0 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
75 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
76 : "invalid contract input type");
77 0 : GNUNET_assert (0);
78 : return NULL;
79 6 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
80 6 : return GNUNET_JSON_PACK (
81 : GNUNET_JSON_pack_string ("type",
82 : "token"),
83 : GNUNET_JSON_pack_string ("token_family_slug",
84 : input->details.token.token_family_slug),
85 : GNUNET_JSON_pack_int64 ("count",
86 : input->details.token.count));
87 : }
88 :
89 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
90 : "unsupported contract input type %d",
91 : input->type);
92 0 : GNUNET_assert (0);
93 : return NULL;
94 : }
95 :
96 :
97 : /**
98 : * Get JSON representation of contract choice output.
99 : *
100 : * @param[in] output contract terms choice output
101 : * @return JSON representation of @a output; NULL on error
102 : */
103 : static json_t *
104 10 : json_from_contract_output (
105 : const struct TALER_MERCHANT_ContractOutput *output)
106 : {
107 10 : switch (output->type)
108 : {
109 0 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
110 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
111 : "invalid contract output type");
112 0 : GNUNET_assert (0);
113 : return NULL;
114 9 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
115 9 : return GNUNET_JSON_PACK (
116 : GNUNET_JSON_pack_string ("type",
117 : "token"),
118 : GNUNET_JSON_pack_string ("token_family_slug",
119 : output->details.token.token_family_slug),
120 : GNUNET_JSON_pack_uint64 ("count",
121 : output->details.token.count),
122 : GNUNET_JSON_pack_uint64 ("key_index",
123 : output->details.token.key_index));
124 1 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
125 : {
126 : json_t *donau_urls;
127 :
128 1 : donau_urls = json_array ();
129 1 : GNUNET_assert (NULL != donau_urls);
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_new (
135 : donau_urls,
136 : json_string (
137 : output->details.donation_receipt.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 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
150 : "Unsupported contract output type %d",
151 : output->type);
152 0 : GNUNET_assert (0);
153 : return NULL;
154 : }
155 :
156 :
157 : json_t *
158 11 : TALER_MERCHANT_json_from_contract_choice (
159 : const struct TALER_MERCHANT_ContractChoice *choice,
160 : bool order)
161 : {
162 : json_t *inputs;
163 : json_t *outputs;
164 :
165 11 : inputs = json_array ();
166 11 : GNUNET_assert (NULL != inputs);
167 17 : for (unsigned int i = 0; i < choice->inputs_len; i++)
168 6 : GNUNET_assert (0 ==
169 : json_array_append_new (inputs,
170 : json_from_contract_input (
171 : &choice->inputs[i])));
172 11 : outputs = json_array ();
173 11 : GNUNET_assert (NULL != outputs);
174 21 : for (unsigned int i = 0; i < choice->outputs_len; i++)
175 10 : GNUNET_assert (0 ==
176 : json_array_append_new (outputs,
177 : json_from_contract_output (
178 : &choice->outputs[i])));
179 :
180 11 : return GNUNET_JSON_PACK (
181 : TALER_JSON_pack_amount ("amount",
182 : &choice->amount),
183 : GNUNET_JSON_pack_allow_null (
184 : GNUNET_JSON_pack_string ("description",
185 : choice->description)),
186 : GNUNET_JSON_pack_allow_null (
187 : GNUNET_JSON_pack_object_incref ("description_i18n",
188 : choice->description_i18n)),
189 : (order)
190 : ? GNUNET_JSON_pack_allow_null (
191 : TALER_JSON_pack_amount (
192 : "max_fee",
193 : /* workaround for nullable amount */
194 : (GNUNET_OK ==
195 : TALER_amount_is_valid (&choice->max_fee))
196 : ? &choice->max_fee
197 : : NULL))
198 : : TALER_JSON_pack_amount ("max_fee",
199 : &choice->max_fee),
200 : (order)
201 : ? GNUNET_JSON_pack_allow_null (
202 : GNUNET_JSON_pack_array_steal ("inputs",
203 : inputs))
204 : : GNUNET_JSON_pack_array_steal ("inputs",
205 : inputs),
206 : (order)
207 : ? GNUNET_JSON_pack_allow_null (
208 : GNUNET_JSON_pack_array_steal ("outputs",
209 : outputs))
210 : : GNUNET_JSON_pack_array_steal ("outputs",
211 : outputs));
212 : }
213 :
214 :
215 : /**
216 : * Get JSON representation of contract token family key.
217 : *
218 : * @param[in] key contract token family key
219 : * @return JSON representation of @a key; NULL on error
220 : */
221 : static json_t *
222 10 : json_from_token_family_key (
223 : const struct TALER_MERCHANT_ContractTokenFamilyKey *key)
224 : {
225 10 : return GNUNET_JSON_PACK (
226 : GNUNET_JSON_pack_timestamp ("signature_validity_start",
227 : key->valid_after),
228 : GNUNET_JSON_pack_timestamp ("signature_validity_end",
229 : key->valid_before),
230 : TALER_JSON_pack_token_pub (NULL,
231 : &key->pub));
232 : }
233 :
234 :
235 : /**
236 : * Get JSON representation of contract token family details.
237 : *
238 : * @param[in] family contract token family
239 : * @return JSON representation of @a family->details; NULL on error
240 : */
241 : static json_t *
242 11 : json_from_token_family_details (
243 : const struct TALER_MERCHANT_ContractTokenFamily *family)
244 : {
245 11 : switch (family->kind)
246 : {
247 0 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
248 0 : break;
249 9 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
250 : {
251 : json_t *trusted_domains;
252 :
253 9 : trusted_domains = json_array ();
254 9 : GNUNET_assert (NULL != trusted_domains);
255 9 : for (unsigned int i = 0;
256 12 : i < family->details.subscription.trusted_domains_len;
257 3 : i++)
258 3 : GNUNET_assert (0 ==
259 : json_array_append_new (
260 : trusted_domains,
261 : json_string (
262 : family->details.subscription.trusted_domains[i])));
263 :
264 9 : return GNUNET_JSON_PACK (
265 : GNUNET_JSON_pack_string ("class",
266 : "subscription"),
267 : GNUNET_JSON_pack_array_steal ("trusted_domains",
268 : trusted_domains));
269 : }
270 2 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
271 : {
272 : json_t *expected_domains;
273 :
274 2 : expected_domains = json_array ();
275 2 : GNUNET_assert (NULL != expected_domains);
276 2 : for (unsigned int i = 0;
277 5 : i < family->details.discount.expected_domains_len;
278 3 : i++)
279 3 : GNUNET_assert (0 ==
280 : json_array_append_new (
281 : expected_domains,
282 : json_string (
283 : family->details.discount.expected_domains[i])));
284 :
285 2 : return GNUNET_JSON_PACK (
286 : GNUNET_JSON_pack_string ("class",
287 : "discount"),
288 : GNUNET_JSON_pack_array_steal ("expected_domains",
289 : expected_domains));
290 : }
291 : }
292 :
293 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
294 : "unsupported token family kind %d",
295 : family->kind);
296 0 : GNUNET_assert (0);
297 : return NULL;
298 : }
299 :
300 :
301 : json_t *
302 11 : TALER_MERCHANT_json_from_token_family (
303 : const struct TALER_MERCHANT_ContractTokenFamily *family)
304 : {
305 : json_t *keys;
306 :
307 11 : keys = json_array ();
308 11 : GNUNET_assert (NULL != keys);
309 21 : for (unsigned int i = 0; i < family->keys_len; i++)
310 10 : GNUNET_assert (0 == json_array_append_new (
311 : keys,
312 : json_from_token_family_key (
313 : &family->keys[i])));
314 :
315 11 : return GNUNET_JSON_PACK (
316 : GNUNET_JSON_pack_string ("name",
317 : family->name),
318 : GNUNET_JSON_pack_string ("description",
319 : family->description),
320 : GNUNET_JSON_pack_object_incref ("description_i18n",
321 : family->description_i18n),
322 : GNUNET_JSON_pack_array_steal ("keys",
323 : keys),
324 : GNUNET_JSON_pack_object_steal ("details",
325 : json_from_token_family_details (family)),
326 : GNUNET_JSON_pack_bool ("critical",
327 : family->critical));
328 : }
329 :
330 :
331 : /**
332 : * Get JSON object with contract terms v0-specific fields.
333 : *
334 : * @param[in] input contract terms v0
335 : * @return JSON object with @a input v0 fields; NULL on error
336 : */
337 : static json_t *
338 1 : json_from_contract_v0 (
339 : const struct TALER_MERCHANT_Contract *input)
340 : {
341 1 : return GNUNET_JSON_PACK (
342 : TALER_JSON_pack_amount ("amount",
343 : &input->details.v0.brutto),
344 : TALER_JSON_pack_amount ("max_fee",
345 : &input->details.v0.max_fee));
346 : }
347 :
348 :
349 : /**
350 : * Get JSON object with contract terms v1-specific fields.
351 : *
352 : * @param[in] input contract terms v1
353 : * @return JSON object with @a input v1 fields; NULL on error
354 : */
355 : static json_t *
356 1 : json_from_contract_v1 (
357 : const struct TALER_MERCHANT_Contract *input)
358 : {
359 : json_t *choices;
360 : json_t *families;
361 :
362 1 : choices = json_array ();
363 1 : GNUNET_assert (0 != choices);
364 2 : for (unsigned i = 0; i < input->details.v1.choices_len; i++)
365 1 : GNUNET_assert (0 == json_array_append_new (
366 : choices,
367 : TALER_MERCHANT_json_from_contract_choice (
368 : &input->details.v1.choices[i],
369 : false)));
370 :
371 1 : families = json_object ();
372 1 : GNUNET_assert (0 != families);
373 3 : for (unsigned i = 0; i < input->details.v1.token_authorities_len; i++)
374 2 : GNUNET_assert (0 == json_object_set_new (
375 : families,
376 : input->details.v1.token_authorities[i].slug,
377 : TALER_MERCHANT_json_from_token_family (
378 : &input->details.v1.token_authorities[i])));
379 :
380 1 : return GNUNET_JSON_PACK (
381 : GNUNET_JSON_pack_array_steal ("choices",
382 : choices),
383 : GNUNET_JSON_pack_object_steal ("token_families",
384 : families));
385 : }
386 :
387 :
388 : /**
389 : * Convert quantity @a q into a string for JSON serialization
390 : *
391 : * @param q quantity to convert
392 : * @return formatted string
393 : */
394 : static const char *
395 20 : quantity_to_string (const struct TALER_MERCHANT_ProductQuantity *q)
396 : {
397 : static char res[64];
398 :
399 20 : TALER_MERCHANT_vk_format_fractional_string (TALER_MERCHANT_VK_QUANTITY,
400 20 : q->integer,
401 20 : q->fractional,
402 : sizeof (res),
403 : res);
404 20 : return res;
405 : }
406 :
407 :
408 : json_t *
409 20 : TALER_MERCHANT_product_sold_serialize (
410 : const struct TALER_MERCHANT_ProductSold *p)
411 : {
412 : json_t *prices;
413 :
414 20 : prices = json_array ();
415 20 : GNUNET_assert (NULL != prices);
416 35 : for (unsigned int i = 0; i<p->prices_length; i++)
417 15 : GNUNET_assert (0 ==
418 : json_array_append_new (prices,
419 : TALER_JSON_from_amount (
420 : &p->prices[i])));
421 20 : return GNUNET_JSON_PACK (
422 : GNUNET_JSON_pack_allow_null (
423 : GNUNET_JSON_pack_string ("product_id",
424 : p->product_id)),
425 : GNUNET_JSON_pack_allow_null (
426 : GNUNET_JSON_pack_string ("product_name",
427 : p->product_name)),
428 : GNUNET_JSON_pack_allow_null (
429 : GNUNET_JSON_pack_string ("description",
430 : p->description)),
431 : GNUNET_JSON_pack_allow_null (
432 : GNUNET_JSON_pack_object_incref ("description_i18n",
433 : (json_t *) p->description_i18n)),
434 : GNUNET_JSON_pack_allow_null (
435 : ( (0 != p->unit_quantity.integer) ||
436 : (0 != p->unit_quantity.fractional) )
437 : ? GNUNET_JSON_pack_string ("unit_quantity",
438 : quantity_to_string (&p->unit_quantity))
439 : : GNUNET_JSON_pack_string ("dummy",
440 : NULL) ),
441 : /* Legacy */
442 : GNUNET_JSON_pack_allow_null (
443 : (0 == p->unit_quantity.fractional)
444 : ? GNUNET_JSON_pack_uint64 ("quantity",
445 : p->unit_quantity.integer)
446 : : GNUNET_JSON_pack_string ("dummy",
447 : NULL) ),
448 : GNUNET_JSON_pack_allow_null (
449 : GNUNET_JSON_pack_string ("unit",
450 : p->unit)),
451 : /* Deprecated, use prices! */
452 : GNUNET_JSON_pack_allow_null (
453 : TALER_JSON_pack_amount ("price",
454 : 0 < p->prices_length
455 : ? &p->prices[0]
456 : : NULL)),
457 : GNUNET_JSON_pack_array_steal ("prices",
458 : prices),
459 : GNUNET_JSON_pack_allow_null (
460 : GNUNET_JSON_pack_string ("image",
461 : p->image)),
462 : GNUNET_JSON_pack_allow_null (
463 : GNUNET_JSON_pack_array_incref ("taxes",
464 : (json_t *) p->taxes)),
465 : GNUNET_JSON_pack_allow_null (
466 : GNUNET_TIME_absolute_is_never (p->delivery_date.abs_time)
467 : ? GNUNET_JSON_pack_string ("dummy",
468 : NULL)
469 : : GNUNET_JSON_pack_timestamp ("delivery_date",
470 : p->delivery_date)),
471 : GNUNET_JSON_pack_uint64 ("product_money_pot",
472 : p->product_money_pot));
473 : }
474 :
475 :
476 : json_t *
477 2 : TALER_MERCHANT_contract_serialize (
478 : const struct TALER_MERCHANT_Contract *input,
479 : bool nonce_optional)
480 : {
481 : json_t *details;
482 : json_t *products;
483 :
484 2 : switch (input->version)
485 : {
486 1 : case TALER_MERCHANT_CONTRACT_VERSION_0:
487 1 : details = json_from_contract_v0 (input);
488 1 : goto success;
489 1 : case TALER_MERCHANT_CONTRACT_VERSION_1:
490 1 : details = json_from_contract_v1 (input);
491 1 : goto success;
492 : }
493 :
494 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
495 : "unknown contract type version %d",
496 : input->version);
497 0 : GNUNET_assert (0);
498 : return NULL;
499 :
500 2 : success:
501 2 : products = json_array ();
502 2 : GNUNET_assert (NULL != products);
503 2 : for (size_t i = 0; i<input->products_len; i++)
504 : {
505 0 : GNUNET_assert (
506 : 0 ==
507 : json_array_append_new (products,
508 : TALER_MERCHANT_product_sold_serialize (
509 : &input->products[i])));
510 : }
511 :
512 2 : return GNUNET_JSON_PACK (
513 : GNUNET_JSON_pack_uint64 ("version",
514 : input->version),
515 : GNUNET_JSON_pack_string ("summary",
516 : input->summary),
517 : GNUNET_JSON_pack_allow_null (
518 : GNUNET_JSON_pack_object_steal ("summary_i18n",
519 : input->summary_i18n)),
520 : GNUNET_JSON_pack_string ("order_id",
521 : input->order_id),
522 : GNUNET_JSON_pack_allow_null (
523 : GNUNET_JSON_pack_string ("public_reorder_url",
524 : input->public_reorder_url)),
525 : GNUNET_JSON_pack_allow_null (
526 : GNUNET_JSON_pack_string ("fulfillment_url",
527 : input->fulfillment_url)),
528 : GNUNET_JSON_pack_allow_null (
529 : GNUNET_JSON_pack_string ("fulfillment_message",
530 : input->fulfillment_message)),
531 : GNUNET_JSON_pack_allow_null (
532 : GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
533 : input->fulfillment_message_i18n)),
534 : GNUNET_JSON_pack_array_steal ("products",
535 : products),
536 : GNUNET_JSON_pack_timestamp ("timestamp",
537 : input->timestamp),
538 : GNUNET_JSON_pack_timestamp ("refund_deadline",
539 : input->refund_deadline),
540 : GNUNET_JSON_pack_timestamp ("pay_deadline",
541 : input->pay_deadline),
542 : GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
543 : input->wire_deadline),
544 : GNUNET_JSON_pack_data_auto ("merchant_pub",
545 : &input->merchant_pub.eddsa_pub),
546 : GNUNET_JSON_pack_string ("merchant_base_url",
547 : input->merchant_base_url),
548 : GNUNET_JSON_pack_object_steal ("merchant",
549 : json_from_merchant_details (input)),
550 : GNUNET_JSON_pack_data_auto ("h_wire",
551 : &input->h_wire),
552 : GNUNET_JSON_pack_string ("wire_method",
553 : input->wire_method),
554 : GNUNET_JSON_pack_array_steal ("exchanges",
555 : input->exchanges),
556 : GNUNET_JSON_pack_allow_null (
557 : GNUNET_JSON_pack_object_steal ("delivery_location",
558 : input->delivery_location)),
559 : GNUNET_JSON_pack_allow_null (
560 : GNUNET_JSON_pack_timestamp ("delivery_date",
561 : input->delivery_date)),
562 : (nonce_optional)
563 : ? GNUNET_JSON_pack_allow_null (
564 : GNUNET_JSON_pack_string ("nonce",
565 : input->nonce))
566 : : GNUNET_JSON_pack_string ("nonce",
567 : input->nonce),
568 : GNUNET_JSON_pack_allow_null (
569 : GNUNET_JSON_pack_time_rel ("auto_refund",
570 : input->auto_refund)),
571 : GNUNET_JSON_pack_allow_null (
572 : GNUNET_JSON_pack_object_steal ("extra",
573 : input->extra)),
574 : GNUNET_JSON_pack_allow_null (
575 : GNUNET_JSON_pack_uint64 ("minimum_age",
576 : input->minimum_age)),
577 : (0 == input->default_money_pot)
578 : ? GNUNET_JSON_pack_allow_null (
579 : GNUNET_JSON_pack_string ("dummy",
580 : NULL))
581 : : GNUNET_JSON_pack_uint64 ("default_money_pot",
582 : input->default_money_pot),
583 : GNUNET_JSON_pack_object_steal (NULL,
584 : details));
585 : }
|