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 "taler/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/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 1 : json_from_contract_input (
70 : const struct TALER_MERCHANT_ContractInput *input)
71 : {
72 1 : 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 1 : case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
80 1 : 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 2 : json_from_contract_output (
105 : const struct TALER_MERCHANT_ContractOutput *output)
106 : {
107 2 : 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 1 : case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
115 1 : 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 1 : 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 1 : inputs = json_array ();
166 1 : GNUNET_assert (NULL != inputs);
167 2 : for (unsigned int i = 0; i < choice->inputs_len; i++)
168 1 : GNUNET_assert (0 ==
169 : json_array_append_new (inputs,
170 : json_from_contract_input (
171 : &choice->inputs[i])));
172 1 : outputs = json_array ();
173 1 : GNUNET_assert (NULL != outputs);
174 3 : for (unsigned int i = 0; i < choice->outputs_len; i++)
175 2 : GNUNET_assert (0 ==
176 : json_array_append_new (outputs,
177 : json_from_contract_output (
178 : &choice->outputs[i])));
179 :
180 1 : return GNUNET_JSON_PACK (
181 : TALER_JSON_pack_amount ("amount",
182 : &choice->amount),
183 : GNUNET_JSON_pack_allow_null (
184 : TALER_JSON_pack_amount ("tip",
185 : choice->no_tip
186 : ? NULL
187 : : &choice->tip)),
188 : GNUNET_JSON_pack_allow_null (
189 : GNUNET_JSON_pack_string ("description",
190 : choice->description)),
191 : GNUNET_JSON_pack_allow_null (
192 : GNUNET_JSON_pack_object_incref ("description_i18n",
193 : choice->description_i18n)),
194 : (order)
195 : ? GNUNET_JSON_pack_allow_null (
196 : TALER_JSON_pack_amount (
197 : "max_fee",
198 : /* workaround for nullable amount */
199 : (GNUNET_OK ==
200 : TALER_amount_is_valid (&choice->max_fee))
201 : ? &choice->max_fee
202 : : NULL))
203 : : TALER_JSON_pack_amount ("max_fee",
204 : &choice->max_fee),
205 : (order)
206 : ? GNUNET_JSON_pack_allow_null (
207 : GNUNET_JSON_pack_array_steal ("inputs",
208 : inputs))
209 : : GNUNET_JSON_pack_array_steal ("inputs",
210 : inputs),
211 : (order)
212 : ? GNUNET_JSON_pack_allow_null (
213 : GNUNET_JSON_pack_array_steal ("outputs",
214 : outputs))
215 : : GNUNET_JSON_pack_array_steal ("outputs",
216 : outputs));
217 : }
218 :
219 :
220 : /**
221 : * Get JSON representation of contract token family key.
222 : *
223 : * @param[in] key contract token family key
224 : * @return JSON representation of @a key; NULL on error
225 : */
226 : static json_t *
227 2 : json_from_token_family_key (
228 : const struct TALER_MERCHANT_ContractTokenFamilyKey *key)
229 : {
230 2 : return GNUNET_JSON_PACK (
231 : GNUNET_JSON_pack_timestamp ("signature_validity_start",
232 : key->valid_after),
233 : GNUNET_JSON_pack_timestamp ("signature_validity_end",
234 : key->valid_before),
235 : TALER_JSON_pack_token_pub (NULL,
236 : &key->pub));
237 : }
238 :
239 :
240 : /**
241 : * Get JSON representation of contract token family details.
242 : *
243 : * @param[in] family contract token family
244 : * @return JSON representation of @a family->details; NULL on error
245 : */
246 : static json_t *
247 2 : json_from_token_family_details (
248 : const struct TALER_MERCHANT_ContractTokenFamily *family)
249 : {
250 2 : switch (family->kind)
251 : {
252 0 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
253 0 : break;
254 1 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
255 : {
256 : json_t *trusted_domains;
257 :
258 1 : trusted_domains = json_array ();
259 1 : GNUNET_assert (NULL != trusted_domains);
260 1 : for (unsigned int i = 0;
261 4 : i < family->details.subscription.trusted_domains_len;
262 3 : i++)
263 3 : GNUNET_assert (0 ==
264 : json_array_append_new (
265 : trusted_domains,
266 : json_string (
267 : family->details.subscription.trusted_domains[i])));
268 :
269 1 : return GNUNET_JSON_PACK (
270 : GNUNET_JSON_pack_string ("class",
271 : "subscription"),
272 : GNUNET_JSON_pack_array_steal ("trusted_domains",
273 : trusted_domains));
274 : }
275 1 : case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
276 : {
277 : json_t *expected_domains;
278 :
279 1 : expected_domains = json_array ();
280 1 : GNUNET_assert (NULL != expected_domains);
281 1 : for (unsigned int i = 0;
282 4 : i < family->details.discount.expected_domains_len;
283 3 : i++)
284 3 : GNUNET_assert (0 ==
285 : json_array_append_new (
286 : expected_domains,
287 : json_string (
288 : family->details.discount.expected_domains[i])));
289 :
290 1 : return GNUNET_JSON_PACK (
291 : GNUNET_JSON_pack_string ("class",
292 : "discount"),
293 : GNUNET_JSON_pack_array_steal ("expected_domains",
294 : expected_domains));
295 : }
296 : }
297 :
298 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
299 : "unsupported token family kind %d",
300 : family->kind);
301 0 : GNUNET_assert (0);
302 : return NULL;
303 : }
304 :
305 :
306 : json_t *
307 2 : TALER_MERCHANT_json_from_token_family (
308 : const struct TALER_MERCHANT_ContractTokenFamily *family)
309 : {
310 : json_t *keys;
311 :
312 2 : keys = json_array ();
313 2 : GNUNET_assert (NULL != keys);
314 4 : for (unsigned int i = 0; i < family->keys_len; i++)
315 2 : GNUNET_assert (0 == json_array_append_new (
316 : keys,
317 : json_from_token_family_key (
318 : &family->keys[i])));
319 :
320 2 : return GNUNET_JSON_PACK (
321 : GNUNET_JSON_pack_string ("name",
322 : family->name),
323 : GNUNET_JSON_pack_string ("description",
324 : family->description),
325 : GNUNET_JSON_pack_object_incref ("description_i18n",
326 : family->description_i18n),
327 : GNUNET_JSON_pack_array_steal ("keys",
328 : keys),
329 : GNUNET_JSON_pack_object_steal ("details",
330 : json_from_token_family_details (family)),
331 : GNUNET_JSON_pack_bool ("critical",
332 : family->critical));
333 : }
334 :
335 :
336 : /**
337 : * Get JSON object with contract terms v0-specific fields.
338 : *
339 : * @param[in] input contract terms v0
340 : * @return JSON object with @a input v0 fields; NULL on error
341 : */
342 : static json_t *
343 1 : json_from_contract_v0 (
344 : const struct TALER_MERCHANT_Contract *input)
345 : {
346 1 : return GNUNET_JSON_PACK (
347 : TALER_JSON_pack_amount ("amount",
348 : &input->details.v0.brutto),
349 : GNUNET_JSON_pack_allow_null (
350 : TALER_JSON_pack_amount ("tip",
351 : input->details.v0.no_tip
352 : ? NULL
353 : : &input->details.v0.tip)),
354 : TALER_JSON_pack_amount ("max_fee",
355 : &input->details.v0.max_fee));
356 : }
357 :
358 :
359 : /**
360 : * Get JSON object with contract terms v1-specific fields.
361 : *
362 : * @param[in] input contract terms v1
363 : * @return JSON object with @a input v1 fields; NULL on error
364 : */
365 : static json_t *
366 1 : json_from_contract_v1 (
367 : const struct TALER_MERCHANT_Contract *input)
368 : {
369 : json_t *choices;
370 : json_t *families;
371 :
372 1 : choices = json_array ();
373 1 : GNUNET_assert (0 != choices);
374 2 : for (unsigned i = 0; i < input->details.v1.choices_len; i++)
375 1 : GNUNET_assert (0 == json_array_append_new (
376 : choices,
377 : TALER_MERCHANT_json_from_contract_choice (
378 : &input->details.v1.choices[i],
379 : false)));
380 :
381 1 : families = json_object ();
382 1 : GNUNET_assert (0 != families);
383 3 : for (unsigned i = 0; i < input->details.v1.token_authorities_len; i++)
384 2 : GNUNET_assert (0 == json_object_set_new (
385 : families,
386 : input->details.v1.token_authorities[i].slug,
387 : TALER_MERCHANT_json_from_token_family (
388 : &input->details.v1.token_authorities[i])));
389 :
390 1 : return GNUNET_JSON_PACK (
391 : GNUNET_JSON_pack_array_steal ("choices",
392 : choices),
393 : GNUNET_JSON_pack_object_steal ("token_families",
394 : families));
395 : }
396 :
397 :
398 : /**
399 : * Convert quantity @a q into a string for JSON serialization
400 : *
401 : * @param q quantity to convert
402 : * @return formatted string
403 : */
404 : static const char *
405 0 : quantity_to_string (const struct TALER_MERCHANT_ProductQuantity *q)
406 : {
407 : static char res[64];
408 :
409 0 : TALER_MERCHANT_vk_format_fractional_string (TALER_MERCHANT_VK_QUANTITY,
410 0 : q->integer,
411 0 : q->fractional,
412 : sizeof (res),
413 : res);
414 0 : return res;
415 : }
416 :
417 :
418 : json_t *
419 0 : TALER_MERCHANT_product_sold_serialize (
420 : const struct TALER_MERCHANT_ProductSold *p)
421 : {
422 : json_t *prices;
423 :
424 0 : prices = json_array ();
425 0 : GNUNET_assert (NULL != prices);
426 0 : for (unsigned int i = 0; i<p->prices_length; i++)
427 0 : GNUNET_assert (0 ==
428 : json_array_append_new (prices,
429 : TALER_JSON_from_amount (
430 : &p->prices[i])));
431 0 : return GNUNET_JSON_PACK (
432 : GNUNET_JSON_pack_allow_null (
433 : GNUNET_JSON_pack_string ("product_id",
434 : p->product_id)),
435 : GNUNET_JSON_pack_allow_null (
436 : GNUNET_JSON_pack_string ("product_name",
437 : p->product_name)),
438 : GNUNET_JSON_pack_allow_null (
439 : GNUNET_JSON_pack_string ("description",
440 : p->description)),
441 : GNUNET_JSON_pack_allow_null (
442 : GNUNET_JSON_pack_object_incref ("description_i18n",
443 : (json_t *) p->description_i18n)),
444 : GNUNET_JSON_pack_allow_null (
445 : ( (0 != p->unit_quantity.integer) ||
446 : (0 != p->unit_quantity.fractional) )
447 : ? GNUNET_JSON_pack_string ("unit_quantity",
448 : quantity_to_string (&p->unit_quantity))
449 : : GNUNET_JSON_pack_string ("dummy",
450 : NULL) ),
451 : /* Legacy */
452 : GNUNET_JSON_pack_allow_null (
453 : (0 == p->unit_quantity.fractional)
454 : ? GNUNET_JSON_pack_uint64 ("quantity",
455 : p->unit_quantity.integer)
456 : : GNUNET_JSON_pack_string ("dummy",
457 : NULL) ),
458 : GNUNET_JSON_pack_allow_null (
459 : GNUNET_JSON_pack_string ("unit",
460 : p->unit)),
461 : /* Deprecated, use prices! */
462 : GNUNET_JSON_pack_allow_null (
463 : TALER_JSON_pack_amount ("price",
464 : 0 < p->prices_length
465 : ? &p->prices[0]
466 : : NULL)),
467 : GNUNET_JSON_pack_array_steal ("prices",
468 : prices),
469 : GNUNET_JSON_pack_allow_null (
470 : GNUNET_JSON_pack_string ("image",
471 : p->image)),
472 : GNUNET_JSON_pack_allow_null (
473 : GNUNET_JSON_pack_array_incref ("taxes",
474 : (json_t *) p->taxes)),
475 : GNUNET_JSON_pack_allow_null (
476 : GNUNET_TIME_absolute_is_never (p->delivery_date.abs_time)
477 : ? GNUNET_JSON_pack_string ("dummy",
478 : NULL)
479 : : GNUNET_JSON_pack_timestamp ("delivery_date",
480 : p->delivery_date)),
481 : GNUNET_JSON_pack_uint64 ("product_money_pot",
482 : p->product_money_pot));
483 : }
484 :
485 :
486 : json_t *
487 2 : TALER_MERCHANT_contract_serialize (
488 : const struct TALER_MERCHANT_Contract *input,
489 : bool nonce_optional)
490 : {
491 : json_t *details;
492 : json_t *products;
493 :
494 2 : switch (input->version)
495 : {
496 1 : case TALER_MERCHANT_CONTRACT_VERSION_0:
497 1 : details = json_from_contract_v0 (input);
498 1 : goto success;
499 1 : case TALER_MERCHANT_CONTRACT_VERSION_1:
500 1 : details = json_from_contract_v1 (input);
501 1 : goto success;
502 : }
503 :
504 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
505 : "unknown contract type version %d",
506 : input->version);
507 0 : GNUNET_assert (0);
508 : return NULL;
509 :
510 2 : success:
511 2 : products = json_array ();
512 2 : GNUNET_assert (NULL != products);
513 2 : for (size_t i = 0; i<input->products_len; i++)
514 : {
515 0 : GNUNET_assert (
516 : 0 ==
517 : json_array_append_new (products,
518 : TALER_MERCHANT_product_sold_serialize (
519 : &input->products[i])));
520 : }
521 :
522 2 : return GNUNET_JSON_PACK (
523 : GNUNET_JSON_pack_uint64 ("version",
524 : input->version),
525 : GNUNET_JSON_pack_string ("summary",
526 : input->summary),
527 : GNUNET_JSON_pack_allow_null (
528 : GNUNET_JSON_pack_object_steal ("summary_i18n",
529 : input->summary_i18n)),
530 : GNUNET_JSON_pack_string ("order_id",
531 : input->order_id),
532 : GNUNET_JSON_pack_allow_null (
533 : GNUNET_JSON_pack_string ("public_reorder_url",
534 : input->public_reorder_url)),
535 : GNUNET_JSON_pack_allow_null (
536 : GNUNET_JSON_pack_string ("fulfillment_url",
537 : input->fulfillment_url)),
538 : GNUNET_JSON_pack_allow_null (
539 : GNUNET_JSON_pack_string ("fulfillment_message",
540 : input->fulfillment_message)),
541 : GNUNET_JSON_pack_allow_null (
542 : GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
543 : input->fulfillment_message_i18n)),
544 : GNUNET_JSON_pack_array_steal ("products",
545 : products),
546 : GNUNET_JSON_pack_timestamp ("timestamp",
547 : input->timestamp),
548 : GNUNET_JSON_pack_timestamp ("refund_deadline",
549 : input->refund_deadline),
550 : GNUNET_JSON_pack_timestamp ("pay_deadline",
551 : input->pay_deadline),
552 : GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
553 : input->wire_deadline),
554 : GNUNET_JSON_pack_data_auto ("merchant_pub",
555 : &input->merchant_pub.eddsa_pub),
556 : GNUNET_JSON_pack_string ("merchant_base_url",
557 : input->merchant_base_url),
558 : GNUNET_JSON_pack_object_steal ("merchant",
559 : json_from_merchant_details (input)),
560 : GNUNET_JSON_pack_data_auto ("h_wire",
561 : &input->h_wire),
562 : GNUNET_JSON_pack_string ("wire_method",
563 : input->wire_method),
564 : GNUNET_JSON_pack_array_steal ("exchanges",
565 : input->exchanges),
566 : GNUNET_JSON_pack_allow_null (
567 : GNUNET_JSON_pack_object_steal ("delivery_location",
568 : input->delivery_location)),
569 : GNUNET_JSON_pack_allow_null (
570 : GNUNET_JSON_pack_timestamp ("delivery_date",
571 : input->delivery_date)),
572 : (nonce_optional)
573 : ? GNUNET_JSON_pack_allow_null (
574 : GNUNET_JSON_pack_string ("nonce",
575 : input->nonce))
576 : : GNUNET_JSON_pack_string ("nonce",
577 : input->nonce),
578 : GNUNET_JSON_pack_allow_null (
579 : GNUNET_JSON_pack_time_rel ("auto_refund",
580 : input->auto_refund)),
581 : GNUNET_JSON_pack_allow_null (
582 : GNUNET_JSON_pack_object_steal ("extra",
583 : input->extra)),
584 : GNUNET_JSON_pack_allow_null (
585 : GNUNET_JSON_pack_uint64 ("minimum_age",
586 : input->minimum_age)),
587 : (0 == input->default_money_pot)
588 : ? GNUNET_JSON_pack_allow_null (
589 : GNUNET_JSON_pack_string ("dummy",
590 : NULL))
591 : : GNUNET_JSON_pack_uint64 ("default_money_pot",
592 : input->default_money_pot),
593 : GNUNET_JSON_pack_object_steal (NULL,
594 : details));
595 : }
|