contract_serialize.c (20798B)
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 json_from_merchant_details ( 39 const struct TALER_MERCHANT_Contract *contract) 40 { 41 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 json_from_contract_input ( 70 const struct TALER_MERCHANT_ContractInput *input) 71 { 72 switch (input->type) 73 { 74 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: 75 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 76 "invalid contract input type"); 77 GNUNET_assert (0); 78 return NULL; 79 case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: 80 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 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 90 "unsupported contract input type %d", 91 input->type); 92 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 json_from_contract_output ( 105 const struct TALER_MERCHANT_ContractOutput *output) 106 { 107 switch (output->type) 108 { 109 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: 110 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 111 "invalid contract output type"); 112 GNUNET_assert (0); 113 return NULL; 114 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: 115 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 case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: 125 { 126 json_t *donau_urls; 127 128 donau_urls = json_array (); 129 GNUNET_assert (NULL != donau_urls); 130 for (unsigned i = 0; 131 i < output->details.donation_receipt.donau_urls_len; 132 i++) 133 GNUNET_assert (0 == 134 json_array_append_new ( 135 donau_urls, 136 json_string ( 137 output->details.donation_receipt.donau_urls[i]))); 138 139 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 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 150 "Unsupported contract output type %d", 151 output->type); 152 GNUNET_assert (0); 153 return NULL; 154 } 155 156 157 json_t * 158 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 inputs = json_array (); 166 GNUNET_assert (NULL != inputs); 167 for (unsigned int i = 0; i < choice->inputs_len; i++) 168 GNUNET_assert (0 == 169 json_array_append_new (inputs, 170 json_from_contract_input ( 171 &choice->inputs[i]))); 172 outputs = json_array (); 173 GNUNET_assert (NULL != outputs); 174 for (unsigned int i = 0; i < choice->outputs_len; i++) 175 GNUNET_assert (0 == 176 json_array_append_new (outputs, 177 json_from_contract_output ( 178 &choice->outputs[i]))); 179 180 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 json_from_token_family_key ( 228 const struct TALER_MERCHANT_ContractTokenFamilyKey *key) 229 { 230 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 json_from_token_family_details ( 248 const struct TALER_MERCHANT_ContractTokenFamily *family) 249 { 250 switch (family->kind) 251 { 252 case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID: 253 break; 254 case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION: 255 { 256 json_t *trusted_domains; 257 258 trusted_domains = json_array (); 259 GNUNET_assert (NULL != trusted_domains); 260 for (unsigned int i = 0; 261 i < family->details.subscription.trusted_domains_len; 262 i++) 263 GNUNET_assert (0 == 264 json_array_append_new ( 265 trusted_domains, 266 json_string ( 267 family->details.subscription.trusted_domains[i]))); 268 269 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 case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT: 276 { 277 json_t *expected_domains; 278 279 expected_domains = json_array (); 280 GNUNET_assert (NULL != expected_domains); 281 for (unsigned int i = 0; 282 i < family->details.discount.expected_domains_len; 283 i++) 284 GNUNET_assert (0 == 285 json_array_append_new ( 286 expected_domains, 287 json_string ( 288 family->details.discount.expected_domains[i]))); 289 290 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 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 299 "unsupported token family kind %d", 300 family->kind); 301 GNUNET_assert (0); 302 return NULL; 303 } 304 305 306 json_t * 307 TALER_MERCHANT_json_from_token_family ( 308 const struct TALER_MERCHANT_ContractTokenFamily *family) 309 { 310 json_t *keys; 311 312 keys = json_array (); 313 GNUNET_assert (NULL != keys); 314 for (unsigned int i = 0; i < family->keys_len; i++) 315 GNUNET_assert (0 == json_array_append_new ( 316 keys, 317 json_from_token_family_key ( 318 &family->keys[i]))); 319 320 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 json_from_contract_v0 ( 344 const struct TALER_MERCHANT_Contract *input) 345 { 346 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 json_from_contract_v1 ( 367 const struct TALER_MERCHANT_Contract *input) 368 { 369 json_t *choices; 370 json_t *families; 371 372 choices = json_array (); 373 GNUNET_assert (0 != choices); 374 for (unsigned i = 0; i < input->details.v1.choices_len; i++) 375 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 families = json_object (); 382 GNUNET_assert (0 != families); 383 for (unsigned i = 0; i < input->details.v1.token_authorities_len; i++) 384 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 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 quantity_to_string (const struct TALER_MERCHANT_ProductQuantity *q) 406 { 407 static char res[64]; 408 409 TALER_MERCHANT_vk_format_fractional_string (TALER_MERCHANT_VK_QUANTITY, 410 q->integer, 411 q->fractional, 412 sizeof (res), 413 res); 414 return res; 415 } 416 417 418 json_t * 419 TALER_MERCHANT_product_sold_serialize ( 420 const struct TALER_MERCHANT_ProductSold *p) 421 { 422 json_t *prices; 423 424 prices = json_array (); 425 GNUNET_assert (NULL != prices); 426 for (unsigned int i = 0; i<p->prices_length; i++) 427 GNUNET_assert (0 == 428 json_array_append_new (prices, 429 TALER_JSON_from_amount ( 430 &p->prices[i]))); 431 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 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 switch (input->version) 495 { 496 case TALER_MERCHANT_CONTRACT_VERSION_0: 497 details = json_from_contract_v0 (input); 498 goto success; 499 case TALER_MERCHANT_CONTRACT_VERSION_1: 500 details = json_from_contract_v1 (input); 501 goto success; 502 } 503 504 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 505 "unknown contract type version %d", 506 input->version); 507 GNUNET_assert (0); 508 return NULL; 509 510 success: 511 products = json_array (); 512 GNUNET_assert (NULL != products); 513 for (size_t i = 0; i<input->products_len; i++) 514 { 515 GNUNET_assert ( 516 0 == 517 json_array_append_new (products, 518 TALER_MERCHANT_product_sold_serialize ( 519 &input->products[i]))); 520 } 521 522 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 }