merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 3abb7c87e299b22e4265f4940164644565a258eb
parent 5ab70d21b9f6ee7e2a5f1bf5fc977b785b46c8bc
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Tue, 11 Feb 2025 15:22:34 +0100

Merge branch 'master' into dev/bohdan-potuzhnyi/donau-integration

Diffstat:
Mconfigure.ac | 4++--
Mdebian/changelog | 6++++++
Mdoc/doxygen/taler.doxy | 2+-
Msrc/backend/taler-merchant-httpd_contract.c | 387-------------------------------------------------------------------------------
Msrc/backend/taler-merchant-httpd_contract.h | 79-------------------------------------------------------------------------------
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 970+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/backend/taler-merchant-httpd_private-post-orders.c | 247++++++++-----------------------------------------------------------------------
Msrc/backend/taler-merchant-httpd_private-post-token-families.c | 4++++
Msrc/backend/taler-merchant-kyccheck.c | 1+
Msrc/backenddb/merchantdb_helper.c | 2++
Msrc/backenddb/pg_get_kyc_status.c | 4+++-
Msrc/backenddb/pg_lookup_token_family.c | 5++++-
Msrc/include/taler_merchant_testing_lib.h | 2+-
Msrc/include/taler_merchant_util.h | 34++++++++++++++++++++++++++++++++--
Msrc/testing/test_merchant_api.c | 2+-
Msrc/testing/test_merchant_order_creation.sh | 4++--
Msrc/util/.gitignore | 1+
Msrc/util/Makefile.am | 3++-
Msrc/util/contract_parse.c | 81++++++++++++++++++++++++++-----------------------------------------------------
Asrc/util/json.c | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
20 files changed, 791 insertions(+), 1218 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # # This file is part of TALER -# Copyright (C) 2014-2024 Taler Systems SA +# Copyright (C) 2014-2025 Taler Systems SA # # TALER is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License as published by the Free Software @@ -18,7 +18,7 @@ # This configure file is in the public domain AC_PREREQ([2.69]) -AC_INIT([taler-merchant],[0.14.4],[taler-bug@gnunet.org]) +AC_INIT([taler-merchant],[0.14.5],[taler-bug@gnunet.org]) AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c]) AC_CONFIG_HEADERS([taler_merchant_config.h]) # support for non-recursive builds diff --git a/debian/changelog b/debian/changelog @@ -1,3 +1,9 @@ +taler-merchant (0.14.5) unstable; urgency=low + + * Release version 0.14.5 + + -- Christian Grothoff <grothoff@taler.net> Mon, 10 Jan 2025 10:35:43 +0200 + taler-merchant (0.14.4) unstable; urgency=low * Release 0.14.4. diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "GNU Taler: Merchant" -PROJECT_NUMBER = 0.14.4 +PROJECT_NUMBER = 0.14.5 PROJECT_LOGO = logo.svg OUTPUT_DIRECTORY = . CREATE_SUBDIRS = YES diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c @@ -26,393 +26,6 @@ #include "taler-merchant-httpd_contract.h" -enum TALER_MERCHANT_ContractInputType -TMH_contract_input_type_from_string (const char *str) -{ - /* For now, only 'token' is the only supported option. */ - if (0 == strcmp ("token", str)) - { - return TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN; - } - - return TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID; -} - - -enum TALER_MERCHANT_ContractOutputType -TMH_contract_output_type_from_string (const char *str) -{ - /* For now, only 'token' is the only supported option. */ - if (0 == strcmp ("token", str)) - { - return TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN; - } - return TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID; -} - - -const char * -TMH_string_from_contract_input_type (enum TALER_MERCHANT_ContractInputType t) -{ - switch (t) - { - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: - return "token"; -#if FUTURE - case TALER_MERCHANT_CONTRACT_INPUT_TYPE_COIN: - return "coin"; -#endif - default: - return "invalid"; - } -} - - -const char * -TMH_string_from_contract_output_type (enum TALER_MERCHANT_ContractOutputType t) -{ - switch (t) - { - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: - return "token"; -#if FUTURE - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN: - return "coin"; -#endif - case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: - return "donation_receipt"; - default: - return "invalid"; - } -} - - -/** - * Parse given JSON object to choices array. - * - * @param cls closure, pointer to array length - * @param root the json array representing the choices - * @param[out] ospec where to write the data - * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error - */ -static enum GNUNET_GenericReturnValue -parse_choices (void *cls, - json_t *root, - struct GNUNET_JSON_Specification *ospec) -{ - struct TALER_MERCHANT_ContractChoice **choices = ospec->ptr; - unsigned int *choices_len = cls; - - if (! json_is_array (root)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - GNUNET_array_grow (*choices, - *choices_len, - json_array_size (root)); - - for (unsigned int i = 0; i<*choices_len; i++) - { - struct TALER_MERCHANT_ContractChoice *choice = &(*choices)[i]; - const json_t *jinputs; - const json_t *joutputs; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("amount", - &choice->amount), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("max_fee", - &choice->max_fee), - NULL), - GNUNET_JSON_spec_array_const ("inputs", - &jinputs), - GNUNET_JSON_spec_array_const ("outputs", - &joutputs), - GNUNET_JSON_spec_end () - }; - const char *error_name; - unsigned int error_line; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (root, i), - spec, - &error_name, - &error_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[error_line].field, - error_line, - error_name); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - const json_t *jinput; - size_t idx; - json_array_foreach ((json_t *) jinputs, idx, jinput) - { - struct TALER_MERCHANT_ContractInput input = { - .details.token.count = 1 - }; - const char *kind; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("kind", - &kind), - GNUNET_JSON_spec_string ("token_family_slug", - &input.details.token.token_family_slug), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("count", - &input.details.token.count), - NULL), - GNUNET_JSON_spec_end () - }; - const char *ierror_name; - unsigned int ierror_line; - - if (GNUNET_OK != - GNUNET_JSON_parse (jinput, - ispec, - &ierror_name, - &ierror_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - ispec[ierror_line].field, - ierror_line, - ierror_name); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - input.type = TMH_contract_input_type_from_string (kind); - - if (TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID == input.type) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'kind' invalid in input #%u\n", - (unsigned int) idx); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (0 == input.details.token.count) - { - /* Ignore inputs with 'number' field set to 0 */ - continue; - } - - GNUNET_array_append (choice->inputs, - choice->inputs_len, - input); - } - } - - { - const json_t *joutput; - size_t idx; - json_array_foreach ((json_t *) joutputs, idx, joutput) - { - struct TALER_MERCHANT_ContractOutput output = { - .details.token.count = 1 - }; - const char *kind; - uint32_t ki; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("kind", - &kind), - GNUNET_JSON_spec_string ("token_family_slug", - // FIXME... - (const char **) &output.details.token. - token_family_slug), - GNUNET_JSON_spec_uint32 ("key_index", - &ki), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("count", - &output.details.token.count), - NULL), - GNUNET_JSON_spec_end () - }; - const char *ierror_name; - unsigned int ierror_line; - - if (GNUNET_OK != - GNUNET_JSON_parse (joutput, - ispec, - &ierror_name, - &ierror_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - ispec[ierror_line].field, - ierror_line, - ierror_name); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - output.details.token.key_index = ki; - output.type = TMH_contract_output_type_from_string (kind); - - if (TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID == output.type) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'kind' invalid in output #%u\n", - (unsigned int) idx); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (0 == output.details.token.count) - { - /* Ignore outputs with 'number' field set to 0 */ - continue; - } - - GNUNET_array_append (choice->outputs, - choice->outputs_len, - output); - } - } - } - return GNUNET_OK; -} - - -struct GNUNET_JSON_Specification -TALER_JSON_spec_choices (const char *name, - struct TALER_MERCHANT_ContractChoice **choices, - unsigned int *choices_len) -{ - struct GNUNET_JSON_Specification ret = { - .cls = (void *) choices_len, - .parser = &parse_choices, - .field = name, - .ptr = choices, - }; - - return ret; -} - - -/** - * Parse given JSON object to token families array. - * - * @param cls closure, pointer to array length - * @param root the json object representing the token families. The keys are - * the token family slugs. - * @param[out] ospec where to write the data - * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error - */ -static enum GNUNET_GenericReturnValue -parse_token_families (void *cls, - json_t *root, - struct GNUNET_JSON_Specification *ospec) -{ - struct TALER_MERCHANT_ContractTokenFamily **families = ospec->ptr; - unsigned int *families_len = cls; - json_t *jfamily; - const char *slug; - - if (! json_is_object (root)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - json_object_foreach (root, slug, jfamily) - { - const json_t *keys; - struct TALER_MERCHANT_ContractTokenFamily family = { - .slug = GNUNET_strdup (slug) - }; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("keys", - &keys), - GNUNET_JSON_spec_bool ("critical", - &family.critical), - GNUNET_JSON_spec_end () - }; - const char *error_name; - unsigned int error_line; - - if (GNUNET_OK != - GNUNET_JSON_parse (jfamily, - spec, - &error_name, - &error_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - spec[error_line].field, - error_line, - error_name); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - GNUNET_array_grow (family.keys, - family.keys_len, - json_array_size (keys)); - - for (unsigned int i = 0; i<family.keys_len; i++) - { - struct TALER_MERCHANT_ContractTokenFamilyKey *key = &family.keys[i]; - struct GNUNET_JSON_Specification key_spec[] = { - TALER_JSON_spec_token_pub ( - "public_key", - &key->pub), - GNUNET_JSON_spec_timestamp ( - "valid_after", - &key->valid_after), - GNUNET_JSON_spec_timestamp ( - "valid_before", - &key->valid_before), - GNUNET_JSON_spec_end () - }; - const char *ierror_name; - unsigned int ierror_line; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (keys, - i), - key_spec, - &ierror_name, - &ierror_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to parse %s at %u: %s\n", - key_spec[ierror_line].field, - ierror_line, - ierror_name); - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - GNUNET_array_append (*families, - *families_len, - family); - } - return GNUNET_OK; -} - - -struct GNUNET_JSON_Specification -TALER_JSON_spec_token_families ( - const char *name, - struct TALER_MERCHANT_ContractTokenFamily **families, - unsigned int *families_len) -{ - struct GNUNET_JSON_Specification ret = { - .cls = (void *) families_len, - .parser = &parse_token_families, - .field = name, - .ptr = families, - }; - - return ret; -} - - enum GNUNET_GenericReturnValue TMH_find_token_family_key ( const char *slug, diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h @@ -28,85 +28,6 @@ #include <jansson.h> -enum TALER_MERCHANT_ContractInputType -TMH_contract_input_type_from_string (const char *str); - - -enum TALER_MERCHANT_ContractOutputType -TMH_contract_output_type_from_string (const char *str); - - -const char * -TMH_string_from_contract_input_type (enum TALER_MERCHANT_ContractInputType t); - - -const char * -TMH_string_from_contract_output_type (enum TALER_MERCHANT_ContractOutputType t); - -/** - * Serialize @a contract to a JSON object, ready to be stored in the database. - * The @a contract can be of v0 or v1. - * - * @param[in] contract contract struct to serialize - * @param[in] instance merchant instance for this contract - * @param[in] exchanges JSON array of exchanges - * @param[out] out serialized contract as JSON object - * @return #GNUNET_OK on success - * #GNUNET_NO if @a contract was not valid - * #GNUNET_SYSERR on failure - */ -enum GNUNET_GenericReturnValue -TMH_serialize_contract (const struct TALER_MERCHANT_Contract *contract, - const struct TMH_MerchantInstance *instance, - json_t *exchanges, - json_t **out); - - -enum GNUNET_GenericReturnValue -TMH_serialize_contract_v0 (const struct TALER_MERCHANT_Contract *contract, - const struct TMH_MerchantInstance *instance, - json_t *exchanges, - json_t **out); - - -enum GNUNET_GenericReturnValue -TMH_serialize_contract_v1 (const struct TALER_MERCHANT_Contract *contract, - const struct TMH_MerchantInstance *instance, - json_t *exchanges, - json_t **out); - -/** - * Provide specification to parse given JSON array to an array - * of contract choices. - * - * @param name name of the choices field in the JSON - * @param[out] choices set to the first element of the array - * @param[out] choices_len set to the length of the @a choices array - * @return spec for parsing a choices array - */ -struct GNUNET_JSON_Specification -TALER_JSON_spec_choices ( - const char *name, - struct TALER_MERCHANT_ContractChoice **choices, - unsigned int *choices_len); - - -/** - * Provide specification to parse given JSON object to an array - * of token families. - * - * @param name name of the token families field in the JSON - * @param[out] families set to the first element of the array - * @param[out] families_len set to the length of the @a families - * @return spec for parsing a token families object - */ -struct GNUNET_JSON_Specification -TALER_JSON_spec_token_families ( - const char *name, - struct TALER_MERCHANT_ContractTokenFamily **families, - unsigned int *families_len); - - /** * Find matching token family in @a families based on @a slug. Then use * @a valid_after to find the matching public key within it. diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2024 Taler Systems SA + (C) 2014-2025 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -89,7 +89,7 @@ enum PayPhase /** * Initial phase where the request is parsed. */ - PP_INIT = 0, + PP_PARSE_PAY = 0, /** * Parse wallet data object from the pay request. @@ -365,8 +365,6 @@ struct ExchangeGroup */ struct PayContext { - // FIXME: group more entries by phase that initializes them, - // like we do for 'validate_tokens'. /** * Stored in a DLL. @@ -379,117 +377,171 @@ struct PayContext struct PayContext *prev; /** - * Array with @e num_exchange exchanges we are depositing - * coins into. + * MHD connection to return to */ - struct ExchangeGroup **egs; + struct MHD_Connection *connection; /** - * Array with @e coins_cnt coins we are despositing. + * Details about the client's request. */ - struct DepositConfirmation *dc; + struct TMH_HandlerContext *hc; /** - * Array with @e tokens_cnt input tokens passed to this request. + * Transaction ID given in @e root. */ - struct TokenUseConfirmation *tokens; + const char *order_id; /** - * Array with @e output_tokens_cnt signed tokens returned in - * the response to the wallet. + * Response to return, NULL if we don't have one yet. */ - struct SignedOutputToken *output_tokens; + struct MHD_Response *response; /** - * Array with @e token_envelopes_cnt (blinded) token envelopes. + * HTTP status code to use for the reply, i.e 200 for "OK". + * Special value UINT_MAX is used to indicate hard errors + * (no reply, return #MHD_NO). */ - struct TokenEnvelope *token_envelopes; + unsigned int response_code; /** - * MHD connection to return to + * Payment processing phase we are in. */ - struct MHD_Connection *connection; + enum PayPhase phase; /** - * Details about the client's request. + * #GNUNET_NO if the @e connection was not suspended, + * #GNUNET_YES if the @e connection was suspended, + * #GNUNET_SYSERR if @e connection was resumed to as + * part of #MH_force_pc_resume during shutdown. */ - struct TMH_HandlerContext *hc; + enum GNUNET_GenericReturnValue suspended; /** - * What wire method (of the @e mi) was selected by the wallet? - * Set in #phase_parse_pay(). + * Results from the phase_parse_pay() */ - struct TMH_WireMethod *wm; + struct + { - /** - * Task called when the (suspended) processing for - * the /pay request times out. - * Happens when we don't get a response from the exchange. - */ - struct GNUNET_SCHEDULER_Task *timeout_task; + /** + * Array with @e num_exchanges exchanges we are depositing + * coins into. + */ + struct ExchangeGroup **egs; - /** - * Response to return, NULL if we don't have one yet. - */ - struct MHD_Response *response; + /** + * Array with @e coins_cnt coins we are despositing. + */ + struct DepositConfirmation *dc; - /** - * Index of selected choice in the @e contract_terms choices array. - */ - int64_t choice_index; + /** + * Array with @e tokens_cnt input tokens passed to this request. + */ + struct TokenUseConfirmation *tokens; - /** - * Our contract (or NULL if not available). - */ - json_t *contract_terms_json; + /** + * Optional session id given in @e root. + * NULL if not given. + */ + char *session_id; + /** + * Wallet data json object from the request. Containing additional + * wallet data such as the selected choice_index. + */ + const json_t *wallet_data; - /** - * Parsed contract terms, NULL when parsing failed. - */ - struct TALER_MERCHANT_Contract *contract_terms; + /** + * Number of coins this payment is made of. Length + * of the @e dc array. + */ + size_t coins_cnt; - /** - * Wallet data json object from the request. Containing additional - * wallet data such as the selected choice_index. - */ - const json_t *wallet_data; + /** + * Number of input tokens passed to this request. Length + * of the @e tokens array. + */ + size_t tokens_cnt; - /** - * Hash of the canonicalized wallet data json object. - */ - struct GNUNET_HashCode h_wallet_data; + /** + * Number of exchanges involved in the payment. Length + * of the @e eg array. + */ + unsigned int num_exchanges; - /** - * Output commitment hash calculated from the 'tokens_evs' field of the request. - */ - struct GNUNET_HashCode h_outputs; + } parse_pay; /** - * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state. + * Results from the phase_wallet_data() */ - void *json_parse_context; + struct + { - /** - * Optional session id given in @e root. - * NULL if not given. - */ - char *session_id; + /** + * Array with @e token_envelopes_cnt (blinded) token envelopes. + */ + struct TokenEnvelope *token_envelopes; - /** - * Transaction ID given in @e root. - */ - const char *order_id; + /** + * Index of selected choice in the @e contract_terms choices array. + */ + int64_t choice_index; - /** - * Serial number of this order in the database (set once we did the lookup). - */ - uint64_t order_serial; + /** + * Number of token envelopes passed to this request. + * Length of the @e token_envelopes array. + */ + size_t token_envelopes_cnt; + + /** + * Hash of the canonicalized wallet data json object. + */ + struct GNUNET_HashCode h_wallet_data; + + } parse_wallet_data; /** - * Hashed proposal. + * Results from the phase_check_contract() */ - struct TALER_PrivateContractHashP h_contract_terms; + struct + { + + /** + * Hashed @e contract_terms. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * Our contract (or NULL if not available). + */ + json_t *contract_terms_json; + + /** + * Parsed contract terms, NULL when parsing failed. + */ + struct TALER_MERCHANT_Contract *contract_terms; + + /** + * What wire method (of the @e mi) was selected by the wallet? + * Set in #phase_parse_pay(). + */ + struct TMH_WireMethod *wm; + + /** + * Set to the POS key, if applicable for this order. + */ + char *pos_key; + + /** + * Serial number of this order in the database (set once we did the lookup). + */ + uint64_t order_serial; + + /** + * Algorithm chosen for generating the confirmation code. + */ + enum TALER_MerchantConfirmationAlgorithm pos_alg; + + } check_contract; /** * Results from the phase_validate_tokens() @@ -512,119 +564,95 @@ struct PayContext */ struct TALER_Amount brutto; - } validate_tokens; - - /** - * Considering all the coins with the "found_in_db" flag - * set, what is the total amount we were so far paid on - * this contract? - */ - struct TALER_Amount total_paid; - - /** - * Considering all the coins with the "found_in_db" flag - * set, what is the total amount we had to pay in deposit - * fees so far on this contract? - */ - struct TALER_Amount total_fees_paid; + /** + * Array with @e output_tokens_len signed tokens returned in + * the response to the wallet. + */ + struct SignedOutputToken *output_tokens; - /** - * Considering all the coins with the "found_in_db" flag - * set, what is the total amount we already refunded? - */ - struct TALER_Amount total_refunded; + /** + * Number of output tokens to return in the response. + * Length of the @e output_tokens array. + */ + unsigned int output_tokens_len; - /** - * Set to the POS key, if applicable for this order. - */ - char *pos_key; + } validate_tokens; /** - * Algorithm chosen for generating the confirmation code. + * Results from the phase_execute_pay_transaction() */ - enum TALER_MerchantConfirmationAlgorithm pos_alg; + struct + { - /** - * Number of coins this payment is made of. Length - * of the @e dc array. - */ - size_t coins_cnt; + /** + * Considering all the coins with the "found_in_db" flag + * set, what is the total amount we were so far paid on + * this contract? + */ + struct TALER_Amount total_paid; - /** - * Number of input tokens passed to this request. Length - * of the @e tokens array. - */ - size_t tokens_cnt; + /** + * Considering all the coins with the "found_in_db" flag + * set, what is the total amount we had to pay in deposit + * fees so far on this contract? + */ + struct TALER_Amount total_fees_paid; - /** - * Number of token envelopes passed to this request. - * Length of the @e token_envelopes array. - */ - size_t token_envelopes_cnt; + /** + * Considering all the coins with the "found_in_db" flag + * set, what is the total amount we already refunded? + */ + struct TALER_Amount total_refunded; - /** - * Number of output tokens to return in the response. - * Length of the @e output_tokens array. - */ - unsigned int output_tokens_len; + /** + * Number of coin deposits pending. + */ + unsigned int pending; - /** - * Number of exchanges involved in the payment. Length - * of the @e eg array. - */ - unsigned int num_exchanges; + /** + * How often have we retried the 'main' transaction? + */ + unsigned int retry_counter; - /** - * How often have we retried the 'main' transaction? - */ - unsigned int retry_counter; + /** + * Set to true if the deposit currency of a coin + * does not match the contract currency. + */ + bool deposit_currency_mismatch; - /** - * Number of batch transactions pending. - */ - unsigned int pending_at_eg; + /** + * Set to true if the database contains a (bogus) + * refund for a different currency. + */ + bool refund_currency_mismatch; - /** - * Number of coin deposits pending. - */ - unsigned int pending; + } pay_transaction; /** - * HTTP status code to use for the reply, i.e 200 for "OK". - * Special value UINT_MAX is used to indicate hard errors - * (no reply, return #MHD_NO). + * Results from the phase_batch_deposits() */ - unsigned int response_code; + struct + { - /** - * Payment processing phase we are in. - */ - enum PayPhase phase; + /** + * Task called when the (suspended) processing for + * the /pay request times out. + * Happens when we don't get a response from the exchange. + */ + struct GNUNET_SCHEDULER_Task *timeout_task; - /** - * #GNUNET_NO if the @e connection was not suspended, - * #GNUNET_YES if the @e connection was suspended, - * #GNUNET_SYSERR if @e connection was resumed to as - * part of #MH_force_pc_resume during shutdown. - */ - enum GNUNET_GenericReturnValue suspended; + /** + * Number of batch transactions pending. + */ + unsigned int pending_at_eg; - /** - * Set to true if the deposit currency of a coin - * does not match the contract currency. - */ - bool deposit_currency_mismatch; + /** + * Did any exchange deny a deposit for legal reasons? + */ + bool got_451; - /** - * Set to true if the database contains a (bogus) - * refund for a different currency. - */ - bool refund_currency_mismatch; + } batch_deposits; - /** - * Did any exchange deny a deposit for legal reasons? - */ - bool got_451; }; @@ -646,10 +674,10 @@ TMH_force_pc_resume () NULL != pc; pc = pc->next) { - if (NULL != pc->timeout_task) + if (NULL != pc->batch_deposits.timeout_task) { - GNUNET_SCHEDULER_cancel (pc->timeout_task); - pc->timeout_task = NULL; + GNUNET_SCHEDULER_cancel (pc->batch_deposits.timeout_task); + pc->batch_deposits.timeout_task = NULL; } if (GNUNET_YES == pc->suspended) { @@ -694,28 +722,28 @@ resume_pay_with_response (struct PayContext *pc, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /pay handling. HTTP status for our reply is %u.\n", response_code); - for (unsigned int i = 0; i<pc->num_exchanges; i++) + for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++) { - struct ExchangeGroup *eg = pc->egs[i]; + struct ExchangeGroup *eg = pc->parse_pay.egs[i]; if (NULL != eg->fo) { TMH_EXCHANGES_keys4exchange_cancel (eg->fo); eg->fo = NULL; - pc->pending_at_eg--; + pc->batch_deposits.pending_at_eg--; } if (NULL != eg->bdh) { TALER_EXCHANGE_batch_deposit_cancel (eg->bdh); eg->bdh = NULL; - pc->pending_at_eg--; + pc->batch_deposits.pending_at_eg--; } } - GNUNET_assert (0 == pc->pending_at_eg); - if (NULL != pc->timeout_task) + GNUNET_assert (0 == pc->batch_deposits.pending_at_eg); + if (NULL != pc->batch_deposits.timeout_task) { - GNUNET_SCHEDULER_cancel (pc->timeout_task); - pc->timeout_task = NULL; + GNUNET_SCHEDULER_cancel (pc->batch_deposits.timeout_task); + pc->batch_deposits.timeout_task = NULL; } pc->phase = PP_RETURN_RESPONSE; pay_resume (pc); @@ -793,13 +821,13 @@ phase_fail_for_legal_reasons (struct PayContext *pc) { json_t *exchanges; - GNUNET_assert (0 == pc->pending); - GNUNET_assert (pc->got_451); + GNUNET_assert (0 == pc->pay_transaction.pending); + GNUNET_assert (pc->batch_deposits.got_451); exchanges = json_array (); GNUNET_assert (NULL != exchanges); - for (unsigned int i = 0; i<pc->num_exchanges; i++) + for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++) { - struct ExchangeGroup *eg = pc->egs[i]; + struct ExchangeGroup *eg = pc->parse_pay.egs[i]; GNUNET_assert (NULL == eg->fo); GNUNET_assert (NULL == eg->bdh); @@ -840,9 +868,9 @@ batch_deposit_transaction (const struct ExchangeGroup *eg, GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (pc->validate_tokens.brutto.currency, &total_without_fees)); - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; struct TALER_Amount amount_without_fees; /* might want to group deposits by batch more explicitly ... */ @@ -864,21 +892,21 @@ batch_deposit_transaction (const struct ExchangeGroup *eg, TMH_db->cls, pc->hc->instance->settings.id, dr->details.ok.deposit_timestamp, - &pc->h_contract_terms, + &pc->check_contract.h_contract_terms, eg->exchange_url, - pc->contract_terms->wire_deadline, + pc->check_contract.contract_terms->wire_deadline, &total_without_fees, &eg->wire_fee, - &pc->wm->h_wire, + &pc->check_contract.wm->h_wire, dr->details.ok.exchange_sig, dr->details.ok.exchange_pub, &b_dep_serial); if (qs <= 0) return qs; /* Entire batch already known or failure, we're done */ - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; /* might want to group deposits by batch more explicitly ... */ if (0 != strcmp (eg->exchange_url, @@ -927,7 +955,7 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing successful payment %s (%s) at instance `%s'\n", pc->hc->infix, - GNUNET_h2s (&pc->h_contract_terms.hash), + GNUNET_h2s (&pc->check_contract.h_contract_terms.hash), pc->hc->instance->settings.id); for (unsigned int r = 0; r<MAX_RETRIES; r++) { @@ -983,17 +1011,17 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, } /* Transaction is done, mark affected coins as complete as well. */ - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; if (0 != strcmp (eg->exchange_url, - pc->dc[i].exchange_url)) + pc->parse_pay.dc[i].exchange_url)) continue; if (dc->found_in_db) continue; dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */ - pc->pending--; + pc->pay_transaction.pending--; } } @@ -1016,8 +1044,8 @@ notify_kyc_required (const struct ExchangeGroup *eg) char *extra; hws = GNUNET_STRINGS_data_to_string_alloc ( - &eg->pc->contract_terms->h_wire, - sizeof (eg->pc->contract_terms->h_wire)); + &eg->pc->check_contract.contract_terms->h_wire, + sizeof (eg->pc->check_contract.contract_terms->h_wire)); GNUNET_asprintf (&extra, "%s %s", hws, @@ -1046,7 +1074,7 @@ batch_deposit_cb ( struct PayContext *pc = eg->pc; eg->bdh = NULL; - pc->pending_at_eg--; + pc->batch_deposits.pending_at_eg--; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Batch deposit completed with status %u\n", dr->hr.http_status); @@ -1056,7 +1084,7 @@ batch_deposit_cb ( case MHD_HTTP_OK: handle_batch_deposit_ok (eg, dr); - if (0 == pc->pending_at_eg) + if (0 == pc->batch_deposits.pending_at_eg) { pc->phase = PP_PAY_TRANSACTION; pay_resume (pc); @@ -1065,20 +1093,20 @@ batch_deposit_cb ( case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: notify_kyc_required (eg); eg->got_451 = true; - pc->got_451 = true; - /* update pc->pending */ - for (size_t i = 0; i<pc->coins_cnt; i++) + pc->batch_deposits.got_451 = true; + /* update pc->pay_transaction.pending */ + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; if (0 != strcmp (eg->exchange_url, - pc->dc[i].exchange_url)) + pc->parse_pay.dc[i].exchange_url)) continue; if (dc->found_in_db) continue; - pc->pending--; + pc->pay_transaction.pending--; } - if (0 == pc->pending_at_eg) + if (0 == pc->batch_deposits.pending_at_eg) { pc->phase = PP_PAY_TRANSACTION; pay_resume (pc); @@ -1172,7 +1200,7 @@ process_pay_with_keys ( struct TALER_Amount max_amount; eg->fo = NULL; - pc->pending_at_eg--; + pc->batch_deposits.pending_at_eg--; GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Processing payment with exchange %s\n", @@ -1204,7 +1232,7 @@ process_pay_with_keys ( TMH_exchange_check_debit ( pc->hc->instance->settings.id, exchange, - pc->wm, + pc->check_contract.wm, &max_amount)) { if (eg->tried_force_keys) @@ -1234,7 +1262,7 @@ process_pay_with_keys ( if (GNUNET_OK != TMH_EXCHANGES_lookup_wire_fee (exchange, - pc->wm->wire_method, + pc->check_contract.wm->wire_method, &eg->wire_fee)) { if (eg->tried_force_keys) @@ -1243,7 +1271,7 @@ process_pay_with_keys ( resume_pay_with_error ( pc, TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED, - pc->wm->wire_method); + pc->check_contract.wm->wire_method); return; } force_keys (eg); @@ -1256,14 +1284,14 @@ process_pay_with_keys ( /* Initiate /batch-deposit operation for all coins of the current exchange (!) */ group_size = 0; - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; const struct TALER_EXCHANGE_DenomPublicKey *denom_details; bool is_age_restricted_denom = false; if (0 != strcmp (eg->exchange_url, - pc->dc[i].exchange_url)) + pc->parse_pay.dc[i].exchange_url)) continue; if (dc->found_in_db) continue; @@ -1318,7 +1346,7 @@ process_pay_with_keys ( is_age_restricted_denom = (0 != denom_details->key.age_mask.bits); if (is_age_restricted_denom && - (0 < pc->contract_terms->minimum_age)) + (0 < pc->check_contract.contract_terms->minimum_age)) { /* Minimum age given and restricted coin provided: We need to verify the * minimum age */ @@ -1342,7 +1370,7 @@ process_pay_with_keys ( if (GNUNET_OK != TALER_age_commitment_verify ( &dc->age_commitment, - pc->contract_terms->minimum_age, + pc->check_contract.contract_terms->minimum_age, &dc->minimum_age_sig)) code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED; AGE_FAIL: @@ -1392,8 +1420,8 @@ AGE_FAIL: GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Group size zero, %u batch transactions remain pending\n", - pc->pending_at_eg); - if (0 == pc->pending_at_eg) + pc->batch_deposits.pending_at_eg); + if (0 == pc->batch_deposits.pending_at_eg) { pc->phase = PP_PAY_TRANSACTION; pay_resume (pc); @@ -1405,24 +1433,24 @@ AGE_FAIL: { struct TALER_EXCHANGE_CoinDepositDetail cdds[group_size]; struct TALER_EXCHANGE_DepositContractDetail dcd = { - .wire_deadline = pc->contract_terms->wire_deadline, - .merchant_payto_uri = pc->wm->payto_uri, - .wire_salt = pc->wm->wire_salt, - .h_contract_terms = pc->h_contract_terms, - .wallet_data_hash = pc->h_wallet_data, - .wallet_timestamp = pc->contract_terms->timestamp, + .wire_deadline = pc->check_contract.contract_terms->wire_deadline, + .merchant_payto_uri = pc->check_contract.wm->payto_uri, + .wire_salt = pc->check_contract.wm->wire_salt, + .h_contract_terms = pc->check_contract.h_contract_terms, + .wallet_data_hash = pc->parse_wallet_data.h_wallet_data, + .wallet_timestamp = pc->check_contract.contract_terms->timestamp, .merchant_pub = hc->instance->merchant_pub, - .refund_deadline = pc->contract_terms->refund_deadline + .refund_deadline = pc->check_contract.contract_terms->refund_deadline }; enum TALER_ErrorCode ec; size_t off = 0; - TALER_merchant_contract_sign (&pc->h_contract_terms, + TALER_merchant_contract_sign (&pc->check_contract.h_contract_terms, &pc->hc->instance->merchant_priv, &dcd.merchant_sig); - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; if (dc->found_in_db) continue; @@ -1435,6 +1463,8 @@ AGE_FAIL: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Initiating batch deposit with %u coins\n", group_size); + /* Note: the coin signatures over the wallet_data_hash are + checked inside of this call */ eg->bdh = TALER_EXCHANGE_batch_deposit ( TMH_curl_ctx, eg->exchange_url, @@ -1460,7 +1490,7 @@ AGE_FAIL: eg->exchange_url))); return; } - pc->pending_at_eg++; + pc->batch_deposits.pending_at_eg++; if (TMH_force_audit) TALER_EXCHANGE_batch_deposit_force_dc (eg->bdh); } @@ -1488,7 +1518,7 @@ force_keys (struct ExchangeGroup *eg) eg->exchange_url); return; } - pc->pending_at_eg++; + pc->batch_deposits.pending_at_eg++; } @@ -1502,7 +1532,7 @@ handle_pay_timeout (void *cls) { struct PayContext *pc = cls; - pc->timeout_task = NULL; + pc->batch_deposits.timeout_task = NULL; GNUNET_assert (GNUNET_YES == pc->suspended); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Resuming pay with error after timeout\n"); @@ -1544,17 +1574,17 @@ get_pay_timeout (unsigned int num_coins) static void phase_batch_deposits (struct PayContext *pc) { - for (unsigned int i = 0; i<pc->num_exchanges; i++) + for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++) { - struct ExchangeGroup *eg = pc->egs[i]; + struct ExchangeGroup *eg = pc->parse_pay.egs[i]; bool have_coins = false; - for (size_t j = 0; j<pc->coins_cnt; j++) + for (size_t j = 0; j<pc->parse_pay.coins_cnt; j++) { - struct DepositConfirmation *dc = &pc->dc[j]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[j]; if (0 != strcmp (eg->exchange_url, - pc->dc[j].exchange_url)) + dc->exchange_url)) continue; if (dc->found_in_db) continue; @@ -1582,9 +1612,9 @@ phase_batch_deposits (struct PayContext *pc) eg->exchange_url)); return; } - pc->pending_at_eg++; + pc->batch_deposits.pending_at_eg++; } - if (0 == pc->pending_at_eg) + if (0 == pc->batch_deposits.pending_at_eg) { pc->phase = PP_PAY_TRANSACTION; pay_resume (pc); @@ -1593,9 +1623,9 @@ phase_batch_deposits (struct PayContext *pc) /* Suspend while we interact with the exchange */ MHD_suspend_connection (pc->connection); pc->suspended = GNUNET_YES; - GNUNET_assert (NULL == pc->timeout_task); - pc->timeout_task - = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt), + GNUNET_assert (NULL == pc->batch_deposits.timeout_task); + pc->batch_deposits.timeout_task + = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->parse_pay.coins_cnt), &handle_pay_timeout, pc); } @@ -1613,7 +1643,7 @@ build_token_sigs (struct PayContext *pc) json_t *token_sigs = json_array (); GNUNET_assert (NULL != token_sigs); - for (unsigned int i = 0; i < pc->output_tokens_len; i++) + for (unsigned int i = 0; i < pc->validate_tokens.output_tokens_len; i++) { GNUNET_assert (0 == json_array_append_new ( @@ -1621,7 +1651,7 @@ build_token_sigs (struct PayContext *pc) GNUNET_JSON_PACK ( GNUNET_JSON_pack_blinded_sig ( "blind_sig", - pc->output_tokens[i].sig.signature) + pc->validate_tokens.output_tokens[i].sig.signature) ))); } return token_sigs; @@ -1642,17 +1672,18 @@ phase_success_response (struct PayContext *pc) /* Sign on our end (as the payment did go through, even if it may have been refunded already) */ - TALER_merchant_pay_sign (&pc->h_contract_terms, + TALER_merchant_pay_sign (&pc->check_contract.h_contract_terms, &pc->hc->instance->merchant_priv, &sig); /* Build the response */ - pos_confirmation = (NULL == pc->pos_key) + pos_confirmation = (NULL == pc->check_contract.pos_key) ? NULL - : TALER_build_pos_confirmation (pc->pos_key, - pc->pos_alg, + : TALER_build_pos_confirmation (pc->check_contract.pos_key, + pc->check_contract.pos_alg, &pc->validate_tokens.brutto, - pc->contract_terms->timestamp); - token_sigs = (0 >= pc->output_tokens_len) + pc->check_contract.contract_terms->timestamp + ); + token_sigs = (0 >= pc->validate_tokens.output_tokens_len) ? NULL : build_token_sigs (pc); pay_end (pc, @@ -1698,8 +1729,8 @@ phase_payment_notification (struct PayContext *pc) NULL, 0); } - if ( (NULL != pc->session_id) && - (NULL != pc->contract_terms->fulfillment_url) ) + if ( (NULL != pc->parse_pay.session_id) && + (NULL != pc->check_contract.contract_terms->fulfillment_url) ) { struct TMH_SessionEventP session_eh = { .header.size = htons (sizeof (session_eh)), @@ -1709,13 +1740,14 @@ phase_payment_notification (struct PayContext *pc) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Notifying clients about session change to %s for %s\n", - pc->session_id, - pc->contract_terms->fulfillment_url); - GNUNET_CRYPTO_hash (pc->session_id, - strlen (pc->session_id), + pc->parse_pay.session_id, + pc->check_contract.contract_terms->fulfillment_url); + GNUNET_CRYPTO_hash (pc->parse_pay.session_id, + strlen (pc->parse_pay.session_id), &session_eh.h_session_id); - GNUNET_CRYPTO_hash (pc->contract_terms->fulfillment_url, - strlen (pc->contract_terms->fulfillment_url), + GNUNET_CRYPTO_hash (pc->check_contract.contract_terms->fulfillment_url, + strlen (pc->check_contract.contract_terms-> + fulfillment_url), &session_eh.h_fulfillment_url); TMH_db->event_notify (TMH_db->cls, &session_eh.header, @@ -1746,9 +1778,9 @@ check_coin_paid (void *cls, { struct PayContext *pc = cls; - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; if (dc->found_in_db) continue; /* processed earlier, skip "expensive" memcmp() */ @@ -1768,29 +1800,29 @@ check_coin_paid (void *cls, "Deposit of coin `%s' already in our DB.\n", TALER_B2S (coin_pub)); if ( (GNUNET_OK != - TALER_amount_cmp_currency (&pc->total_paid, + TALER_amount_cmp_currency (&pc->pay_transaction.total_paid, amount_with_fee)) || (GNUNET_OK != - TALER_amount_cmp_currency (&pc->total_fees_paid, + TALER_amount_cmp_currency (&pc->pay_transaction.total_fees_paid, deposit_fee)) ) { GNUNET_break_op (0); - pc->deposit_currency_mismatch = true; + pc->pay_transaction.deposit_currency_mismatch = true; break; } GNUNET_assert (0 <= - TALER_amount_add (&pc->total_paid, - &pc->total_paid, + TALER_amount_add (&pc->pay_transaction.total_paid, + &pc->pay_transaction.total_paid, amount_with_fee)); GNUNET_assert (0 <= - TALER_amount_add (&pc->total_fees_paid, - &pc->total_fees_paid, + TALER_amount_add (&pc->pay_transaction.total_fees_paid, + &pc->pay_transaction.total_fees_paid, deposit_fee)); dc->deposit_fee = *deposit_fee; dc->refund_fee = *refund_fee; dc->cdd.amount = *amount_with_fee; dc->found_in_db = true; - pc->pending--; + pc->pay_transaction.pending--; } } @@ -1820,25 +1852,25 @@ check_coin_refunded (void *cls, an abort-pay refund (an unusual but possible case), we need to make sure that existing refunds are accounted for. */ - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; /* Get matching coins from results. */ if (0 != GNUNET_memcmp (coin_pub, &dc->cdd.coin_pub)) continue; if (GNUNET_OK != - TALER_amount_cmp_currency (&pc->total_refunded, + TALER_amount_cmp_currency (&pc->pay_transaction.total_refunded, refund_amount)) { GNUNET_break (0); - pc->refund_currency_mismatch = true; + pc->pay_transaction.refund_currency_mismatch = true; break; } GNUNET_assert (0 <= - TALER_amount_add (&pc->total_refunded, - &pc->total_refunded, + TALER_amount_add (&pc->pay_transaction.total_refunded, + &pc->pay_transaction.total_refunded, refund_amount)); break; } @@ -1861,16 +1893,16 @@ check_payment_sufficient (struct PayContext *pc) struct TALER_Amount total_wire_fee; struct TALER_Amount total_needed; - if (0 == pc->coins_cnt) + if (0 == pc->parse_pay.coins_cnt) return TALER_amount_is_zero (&pc->validate_tokens.brutto); GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (pc->validate_tokens.brutto.currency, &total_wire_fee)); - for (unsigned int i = 0; i < pc->num_exchanges; i++) + for (unsigned int i = 0; i < pc->parse_pay.num_exchanges; i++) { if (GNUNET_OK != TALER_amount_cmp_currency (&total_wire_fee, - &pc->egs[i]->wire_fee)) + &pc->parse_pay.egs[i]->wire_fee)) { GNUNET_break_op (0); pay_end (pc, @@ -1883,7 +1915,7 @@ check_payment_sufficient (struct PayContext *pc) if (0 > TALER_amount_add (&total_wire_fee, &total_wire_fee, - &pc->egs[i]->wire_fee)) + &pc->parse_pay.egs[i]->wire_fee)) { GNUNET_break (0); pay_end (pc, @@ -1906,9 +1938,9 @@ check_payment_sufficient (struct PayContext *pc) GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (pc->validate_tokens.brutto.currency, &acc_amount)); - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; GNUNET_assert (dc->found_in_db); if ( (GNUNET_OK != @@ -1975,7 +2007,7 @@ check_payment_sufficient (struct PayContext *pc) TALER_amount2s (&pc->validate_tokens.max_fee)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Total refunded amount: %s\n", - TALER_amount2s (&pc->total_refunded)); + TALER_amount2s (&pc->pay_transaction.total_refunded)); /* Now compare exchange wire fee compared to * what we are willing to pay */ @@ -2049,11 +2081,11 @@ check_payment_sufficient (struct PayContext *pc) /* Do not count refunds towards the payment */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Subtracting total refunds from paid amount: %s\n", - TALER_amount2s (&pc->total_refunded)); + TALER_amount2s (&pc->pay_transaction.total_refunded)); if (0 > TALER_amount_subtract (&final_amount, &acc_amount, - &pc->total_refunded)) + &pc->pay_transaction.total_refunded)) { GNUNET_break (0); pay_end (pc, @@ -2119,13 +2151,13 @@ phase_execute_pay_transaction (struct PayContext *pc) struct TMH_HandlerContext *hc = pc->hc; const char *instance_id = hc->instance->settings.id; - if (pc->got_451) + if (pc->batch_deposits.got_451) { pc->phase = PP_FAIL_LEGAL_REASONS; return; } /* Avoid re-trying transactions on soft errors forever! */ - if (pc->retry_counter++ > MAX_RETRIES) + if (pc->pay_transaction.retry_counter++ > MAX_RETRIES) { GNUNET_break (0); pay_end (pc, @@ -2141,16 +2173,16 @@ phase_execute_pay_transaction (struct PayContext *pc) and check_payment_sufficient()). */ GNUNET_break (GNUNET_OK == TALER_amount_set_zero (pc->validate_tokens.brutto.currency, - &pc->total_paid)); + &pc->pay_transaction.total_paid)); GNUNET_break (GNUNET_OK == TALER_amount_set_zero (pc->validate_tokens.brutto.currency, - &pc->total_fees_paid)); + &pc->pay_transaction.total_fees_paid)); GNUNET_break (GNUNET_OK == TALER_amount_set_zero (pc->validate_tokens.brutto.currency, - &pc->total_refunded)); - for (size_t i = 0; i<pc->coins_cnt; i++) - pc->dc[i].found_in_db = false; - pc->pending = pc->coins_cnt; + &pc->pay_transaction.total_refunded)); + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) + pc->parse_pay.dc[i].found_in_db = false; + pc->pay_transaction.pending = pc->parse_pay.coins_cnt; /* First, try to see if we have all we need already done */ TMH_db->preflight (TMH_db->cls); @@ -2167,16 +2199,15 @@ phase_execute_pay_transaction (struct PayContext *pc) return; } - for (size_t i = 0; i<pc->tokens_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.tokens_cnt; i++) { - struct TokenUseConfirmation *tuc = &pc->tokens[i]; - + struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[i]; enum GNUNET_DB_QueryStatus qs; /* Insert used token into database, the unique constraint will case an error if this token was used before. */ qs = TMH_db->insert_spent_token (TMH_db->cls, - &pc->h_contract_terms, + &pc->check_contract.h_contract_terms, &tuc->h_issue, &tuc->pub, &tuc->sig, @@ -2215,7 +2246,7 @@ phase_execute_pay_transaction (struct PayContext *pc) /* Check if some of these coins already succeeded for _this_ contract. */ qs = TMH_db->lookup_deposits (TMH_db->cls, instance_id, - &pc->h_contract_terms, + &pc->check_contract.h_contract_terms, &check_coin_paid, pc); if (0 > qs) @@ -2232,7 +2263,7 @@ phase_execute_pay_transaction (struct PayContext *pc) "lookup deposits")); return; } - if (pc->deposit_currency_mismatch) + if (pc->pay_transaction.deposit_currency_mismatch) { TMH_db->rollback (TMH_db->cls); GNUNET_break_op (0); @@ -2252,7 +2283,7 @@ phase_execute_pay_transaction (struct PayContext *pc) /* Check if we refunded some of the coins */ qs = TMH_db->lookup_refunds (TMH_db->cls, instance_id, - &pc->h_contract_terms, + &pc->check_contract.h_contract_terms, &check_coin_refunded, pc); if (0 > qs) @@ -2269,7 +2300,7 @@ phase_execute_pay_transaction (struct PayContext *pc) "lookup refunds")); return; } - if (pc->refund_currency_mismatch) + if (pc->pay_transaction.refund_currency_mismatch) { TMH_db->rollback (TMH_db->cls); pay_end (pc, @@ -2282,7 +2313,7 @@ phase_execute_pay_transaction (struct PayContext *pc) } /* Check if there are coins that still need to be processed */ - if (0 != pc->pending) + if (0 != pc->pay_transaction.pending) { /* we made no DB changes, so we can just rollback */ TMH_db->rollback (TMH_db->cls); @@ -2293,7 +2324,7 @@ phase_execute_pay_transaction (struct PayContext *pc) return; } - /* 0 == pc->pending: all coins processed, let's see if that was enough */ + /* 0 == pc->pay_transaction.pending: all coins processed, let's see if that was enough */ if (! check_payment_sufficient (pc)) { /* check_payment_sufficient() will have queued an error already. @@ -2305,14 +2336,14 @@ phase_execute_pay_transaction (struct PayContext *pc) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order `%s' (%s) was fully paid\n", pc->order_id, - GNUNET_h2s (&pc->h_contract_terms.hash)); + GNUNET_h2s (&pc->check_contract.h_contract_terms.hash)); { enum GNUNET_DB_QueryStatus qs; qs = TMH_db->mark_contract_paid (TMH_db->cls, instance_id, - &pc->h_contract_terms, - pc->session_id); + &pc->check_contract.h_contract_terms, + pc->parse_pay.session_id); if (qs < 0) { TMH_db->rollback (TMH_db->cls); @@ -2329,14 +2360,14 @@ phase_execute_pay_transaction (struct PayContext *pc) } /* Store signed output tokens in database. */ - for (size_t i = 0; i<pc->output_tokens_len; i++) + for (size_t i = 0; i<pc->validate_tokens.output_tokens_len; i++) { - struct SignedOutputToken *output = &pc->output_tokens[i]; + struct SignedOutputToken *output = &pc->validate_tokens.output_tokens[i]; enum GNUNET_DB_QueryStatus qs; qs = TMH_db->insert_issued_token (TMH_db->cls, - &pc->h_contract_terms, + &pc->check_contract.h_contract_terms, &output->h_issue, &output->sig); @@ -2358,15 +2389,15 @@ phase_execute_pay_transaction (struct PayContext *pc) TMH_notify_order_change (hc->instance, TMH_OSF_CLAIMED | TMH_OSF_PAID, - pc->contract_terms->timestamp, - pc->order_serial); + pc->check_contract.contract_terms->timestamp, + pc->check_contract.order_serial); { enum GNUNET_DB_QueryStatus qs; json_t *jhook; jhook = GNUNET_JSON_PACK ( GNUNET_JSON_pack_object_incref ("contract_terms", - pc->contract_terms_json), + pc->check_contract.contract_terms_json), GNUNET_JSON_pack_string ("order_id", pc->order_id) ); @@ -2436,7 +2467,7 @@ find_valid_input_tokens ( for (unsigned int j = 0; j < expected_num; j++) { - struct TokenUseConfirmation *tuc = &pc->tokens[index + j]; + struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[index + j]; const struct TALER_MERCHANT_ContractTokenFamilyKey *key = NULL; for (unsigned int i=0; i<family->keys_len; i++) @@ -2449,7 +2480,6 @@ find_valid_input_tokens ( { continue; /* ki currently not valid */ } - // FIXME: tuc->h_issue.hash is NOT initialized here! if (0 == GNUNET_memcmp (&ki->pub.public_key->pub_key_hash, &tuc->h_issue.hash)) @@ -2491,8 +2521,8 @@ find_valid_input_tokens ( } if (GNUNET_OK != - TALER_wallet_token_use_verify (&pc->h_contract_terms, - &pc->h_wallet_data, + TALER_wallet_token_use_verify (&pc->check_contract.h_contract_terms, + &pc->parse_wallet_data.h_wallet_data, &tuc->pub, &tuc->sig)) { @@ -2535,12 +2565,35 @@ find_valid_input_tokens ( /** + * Check if an output token of the given @a tfk is mandatory, or if + * wallets are allowed to simply not support it and still proceed. + * + * @param tfk token family kind to check + * @return true if such outputs are mandatory and wallets must supply + * the corresponding blinded input + */ +static bool +test_tfk_mandatory (enum TALER_MERCHANTDB_TokenFamilyKind tfk) +{ + switch (tfk) + { + case TALER_MERCHANTDB_TFK_Discount: + return false; + case TALER_MERCHANTDB_TFK_Subscription: + return true; + } + GNUNET_break (0); + return false; +} + + +/** * Sign the tokens provided by the wallet for a particular @a key. * * @param[in,out] payment we are processing * @param key token family data * @param priv private key to use to sign with - * @param critical true if the token must exist, if false + * @param mandatory true if the token must exist, if false * and the client did not provide an envelope, that's OK and * we just also skimp on the signature * @param index offset in the token envelope array (from other families) @@ -2552,7 +2605,7 @@ static enum GNUNET_GenericReturnValue sign_token_envelopes (struct PayContext *pc, struct TALER_MERCHANT_ContractTokenFamilyKey *key, struct TALER_TokenIssuePrivateKey *priv, - bool critical, + bool mandatory, unsigned int index, unsigned int expected_num) { @@ -2561,28 +2614,30 @@ sign_token_envelopes (struct PayContext *pc, for (unsigned int j = 0; j<expected_num; j++) { unsigned int pos = index + j; - const struct TokenEnvelope *env = &pc->token_envelopes[pos]; - struct SignedOutputToken *output = &pc->output_tokens[pos]; + const struct TokenEnvelope *env + = &pc->parse_wallet_data.token_envelopes[pos]; + struct SignedOutputToken *output + = &pc->validate_tokens.output_tokens[pos]; - if ( (pos >= pc->token_envelopes_cnt) || - (pos >= pc->output_tokens_len) ) + if ( (pos >= pc->parse_wallet_data.token_envelopes_cnt) || + (pos >= pc->validate_tokens.output_tokens_len) ) { GNUNET_assert (0); /* this should not happen */ return GNUNET_NO; } if (NULL == env->blinded_token.blinded_pub) { - if (! critical) + if (! mandatory) continue; - /* critical token families require a token envelope. */ + /* mandatory token families require a token envelope. */ GNUNET_break_op (0); pay_end (pc, TALER_MHD_reply_with_error ( pc->connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "Token envelope for critical token family missing")); + "Token envelope for mandatory token family missing")); return GNUNET_NO; } TALER_token_issue_sign (priv, @@ -2627,11 +2682,11 @@ find_family (const struct PayContext *pc, const char *slug) { for (unsigned int i = 0; - i < pc->contract_terms->details.v1.token_authorities_len; + i < pc->check_contract.contract_terms->details.v1.token_authorities_len; i++) { const struct TALER_MERCHANT_ContractTokenFamily *tfi - = &pc->contract_terms->details.v1.token_authorities[i]; + = &pc->check_contract.contract_terms->details.v1.token_authorities[i]; if (0 == strcmp (tfi->slug, slug)) @@ -2658,18 +2713,21 @@ find_family (const struct PayContext *pc, static void phase_validate_tokens (struct PayContext *pc) { - switch (pc->contract_terms->version) + switch (pc->check_contract.contract_terms->version) { case TALER_MERCHANT_CONTRACT_VERSION_0: /* No tokens to validate */ pc->phase = PP_PAY_TRANSACTION; - pc->validate_tokens.max_fee = pc->contract_terms->details.v0.max_fee; - pc->validate_tokens.brutto = pc->contract_terms->details.v0.brutto; + pc->validate_tokens.max_fee + = pc->check_contract.contract_terms->details.v0.max_fee; + pc->validate_tokens.brutto + = pc->check_contract.contract_terms->details.v0.brutto; break; case TALER_MERCHANT_CONTRACT_VERSION_1: { const struct TALER_MERCHANT_ContractChoice *selected - = &pc->contract_terms->details.v1.choices[pc->choice_index]; + = &pc->check_contract.contract_terms->details.v1.choices[ + pc->parse_wallet_data.choice_index]; pc->validate_tokens.max_fee = selected->max_fee; pc->validate_tokens.brutto = selected->amount; @@ -2712,8 +2770,8 @@ phase_validate_tokens (struct PayContext *pc) } } - GNUNET_array_grow (pc->output_tokens, - pc->output_tokens_len, + GNUNET_array_grow (pc->validate_tokens.output_tokens, + pc->validate_tokens.output_tokens_len, selected->outputs_len); for (unsigned int i = 0; i<selected->outputs_len; i++) @@ -2764,8 +2822,8 @@ phase_validate_tokens (struct PayContext *pc) TMH_db->cls, pc->hc->instance->settings.id, family->slug, - pc->contract_terms->timestamp, - pc->contract_terms->pay_deadline, + pc->check_contract.contract_terms->timestamp, + pc->check_contract.contract_terms->pay_deadline, &details); if (qs <= 0) { @@ -2773,9 +2831,11 @@ phase_validate_tokens (struct PayContext *pc) GNUNET_ERROR_TYPE_ERROR, "Did not find key for %s at [%llu,%llu]\n", family->slug, - (unsigned long long) pc->contract_terms->timestamp.abs_time. + (unsigned long long) pc->check_contract.contract_terms->timestamp. + abs_time. abs_value_us, - (unsigned long long) pc->contract_terms->pay_deadline.abs_time. + (unsigned long long) pc->check_contract.contract_terms->pay_deadline + .abs_time. abs_value_us); GNUNET_break (0); pay_end (pc, @@ -2788,16 +2848,14 @@ phase_validate_tokens (struct PayContext *pc) } GNUNET_assert (NULL != details.priv.private_key); - if (GNUNET_OK != - sign_token_envelopes (pc, - key, - &details.priv, - /* FIXME: Use critical field stored in database here instead. */ - details.token_family.kind == - TALER_MERCHANTDB_TFK_Subscription, - i, - output->details.token.count)) + sign_token_envelopes ( + pc, + key, + &details.priv, + test_tfk_mandatory (details.token_family.kind), + i, + output->details.token.count)) { /* Error is already scheduled from sign_token_envelopes. */ return; @@ -2806,9 +2864,9 @@ phase_validate_tokens (struct PayContext *pc) } } - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - const struct DepositConfirmation *dc = &pc->dc[i]; + const struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; if (GNUNET_OK != TALER_amount_cmp_currency (&dc->cdd.amount, @@ -2817,7 +2875,7 @@ phase_validate_tokens (struct PayContext *pc) GNUNET_break_op (0); fprintf (stderr, "HERE (%u): %s != %s\n", - (unsigned int) pc->contract_terms->version, + (unsigned int) pc->check_contract.contract_terms->version, dc->cdd.amount.currency, TALER_amount2s (&pc->validate_tokens.brutto)); pay_end (pc, @@ -2860,9 +2918,9 @@ deposit_paid_check ( { struct PayContext *pc = cls; - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dci = &pc->dc[i]; + struct DepositConfirmation *dci = &pc->parse_pay.dc[i]; if ( (0 == GNUNET_memcmp (&dci->cdd.coin_pub, @@ -2896,9 +2954,9 @@ input_tokens_paid_check ( { struct PayContext *pc = cls; - for (size_t i = 0; i<pc->tokens_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.tokens_cnt; i++) { - struct TokenUseConfirmation *tuc = &pc->tokens[i]; + struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[i]; if ( (0 == GNUNET_memcmp (&tuc->pub, use_pub)) && @@ -2930,7 +2988,7 @@ phase_contract_paid (struct PayContext *pc) enum GNUNET_DB_QueryStatus qs; qs = TMH_db->lookup_deposits_by_order (TMH_db->cls, - pc->order_serial, + pc->check_contract.order_serial, &deposit_paid_check, pc); /* Since orders with choices can have a price of zero, @@ -2947,9 +3005,9 @@ phase_contract_paid (struct PayContext *pc) return; } } - for (size_t i = 0; i<pc->coins_cnt && ! unmatched; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt && ! unmatched; i++) { - struct DepositConfirmation *dci = &pc->dc[i]; + struct DepositConfirmation *dci = &pc->parse_pay.dc[i]; if (! dci->matched_in_db) unmatched = true; @@ -2958,9 +3016,9 @@ phase_contract_paid (struct PayContext *pc) { enum GNUNET_DB_QueryStatus qs; - /* FIXME: Use h_contract instead of order_serial here? */ + /* FIXME-Optimization: Maybe use h_contract instead of order_serial here? */ qs = TMH_db->lookup_spent_tokens_by_order (TMH_db->cls, - pc->order_serial, + pc->check_contract.order_serial, &input_tokens_paid_check, pc); @@ -2976,50 +3034,39 @@ phase_contract_paid (struct PayContext *pc) return; } } - for (size_t i = 0; i<pc->tokens_cnt && ! unmatched; i++) + for (size_t i = 0; i<pc->parse_pay.tokens_cnt && ! unmatched; i++) { - struct TokenUseConfirmation *tuc = &pc->tokens[i]; + struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[i]; if (! tuc->found_in_db) unmatched = true; } if (! unmatched) { - /* Everything fine, idempotent request */ - struct TALER_MerchantSignatureP sig; - + /* Everything fine, idempotent request, generate response immediately */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Idempotent pay request for order `%s', signing again\n", pc->order_id); - TALER_merchant_pay_sign (&pc->h_contract_terms, - &pc->hc->instance->merchant_priv, - &sig); - /* FIXME: Add token_sigs to response body. */ - pay_end (pc, - TALER_MHD_REPLY_JSON_PACK ( - pc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ("sig", - &sig))); + pc->phase = PP_SUCCESS_RESPONSE; return; } /* Conflict, double-payment detected! */ - /* FIXME: What should we do with input tokens? + /* FIXME-#8674: What should we do with input tokens? Currently there is no refund for tokens. */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client attempted to pay extra for already paid order `%s'\n", pc->order_id); refunds = json_array (); GNUNET_assert (NULL != refunds); - for (size_t i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dci = &pc->dc[i]; + struct DepositConfirmation *dci = &pc->parse_pay.dc[i]; struct TALER_MerchantSignatureP merchant_sig; if (dci->matched_in_db) continue; TALER_merchant_refund_sign (&dci->cdd.coin_pub, - &pc->h_contract_terms, + &pc->check_contract.h_contract_terms, 0, /* rtransaction id */ &dci->cdd.amount, &pc->hc->instance->merchant_priv, @@ -3064,27 +3111,25 @@ phase_check_contract (struct PayContext *pc) enum GNUNET_DB_QueryStatus qs; bool paid = false; - if (NULL != pc->contract_terms_json) + if (NULL != pc->check_contract.contract_terms_json) { - json_decref (pc->contract_terms_json); - pc->contract_terms_json = NULL; + json_decref (pc->check_contract.contract_terms_json); + pc->check_contract.contract_terms_json = NULL; } - - if (NULL != pc->contract_terms) + if (NULL != pc->check_contract.contract_terms) { - TALER_MERCHANT_contract_free (pc->contract_terms); - pc->contract_terms = NULL; + TALER_MERCHANT_contract_free (pc->check_contract.contract_terms); + pc->check_contract.contract_terms = NULL; } - qs = TMH_db->lookup_contract_terms2 (TMH_db->cls, pc->hc->instance->settings.id, pc->order_id, - &pc->contract_terms_json, - &pc->order_serial, + &pc->check_contract.contract_terms_json, + &pc->check_contract.order_serial, &paid, NULL, - &pc->pos_key, - &pc->pos_alg); + &pc->check_contract.pos_key, + &pc->check_contract.pos_alg); if (0 > qs) { /* single, read-only SQL statements should never cause @@ -3111,12 +3156,14 @@ phase_check_contract (struct PayContext *pc) return; } /* hash contract (needed later) */ - json_dumpf (pc->contract_terms_json, +#if DEBUG + json_dumpf (pc->check_contract.contract_terms_json, stderr, JSON_INDENT (2)); +#endif if (GNUNET_OK != - TALER_JSON_contract_hash (pc->contract_terms_json, - &pc->h_contract_terms)) + TALER_JSON_contract_hash (pc->check_contract.contract_terms_json, + &pc->check_contract.h_contract_terms)) { GNUNET_break (0); pay_end (pc, @@ -3138,13 +3185,13 @@ phase_check_contract (struct PayContext *pc) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling payment for order `%s' with contract hash `%s'\n", pc->order_id, - GNUNET_h2s (&pc->h_contract_terms.hash)); + GNUNET_h2s (&pc->check_contract.h_contract_terms.hash)); - pc->contract_terms = TALER_MERCHANT_contract_parse ( - pc->contract_terms_json, + pc->check_contract.contract_terms = TALER_MERCHANT_contract_parse ( + pc->check_contract.contract_terms_json, true); - if (NULL == pc->contract_terms) + if (NULL == pc->check_contract.contract_terms) { /* invalid contract */ GNUNET_break (0); @@ -3159,11 +3206,11 @@ phase_check_contract (struct PayContext *pc) /* Get details from contract and check fundamentals */ { - switch (pc->contract_terms->version) + switch (pc->check_contract.contract_terms->version) { case TALER_MERCHANT_CONTRACT_VERSION_0: { - if (pc->choice_index > 0) + if (pc->parse_wallet_data.choice_index > 0) { GNUNET_break (0); pay_end (pc, @@ -3178,7 +3225,7 @@ phase_check_contract (struct PayContext *pc) break; case TALER_MERCHANT_CONTRACT_VERSION_1: { - if (pc->choice_index < 0) + if (pc->parse_wallet_data.choice_index < 0) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order `%s' has non-empty choices array but" @@ -3193,14 +3240,15 @@ phase_check_contract (struct PayContext *pc) NULL)); return; } - if (pc->choice_index >= pc->contract_terms->details.v1.choices_len) + if (pc->parse_wallet_data.choice_index >= + pc->check_contract.contract_terms->details.v1.choices_len) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order `%s' has choices array with %u elements but " "request has 'choice_index' field with value %ld\n", pc->order_id, - pc->contract_terms->details.v1.choices_len, - pc->choice_index); + pc->check_contract.contract_terms->details.v1.choices_len, + pc->parse_wallet_data.choice_index); GNUNET_break (0); pay_end (pc, TALER_MHD_reply_with_error ( @@ -3225,10 +3273,11 @@ phase_check_contract (struct PayContext *pc) } } - - if (GNUNET_TIME_timestamp_cmp (pc->contract_terms->wire_deadline, + if (GNUNET_TIME_timestamp_cmp (pc->check_contract.contract_terms-> + wire_deadline, <, - pc->contract_terms->refund_deadline)) + pc->check_contract.contract_terms-> + refund_deadline)) { /* This should already have been checked when creating the order! */ GNUNET_break (0); @@ -3240,7 +3289,8 @@ phase_check_contract (struct PayContext *pc) NULL)); return; } - if (GNUNET_TIME_absolute_is_past (pc->contract_terms->pay_deadline.abs_time)) + if (GNUNET_TIME_absolute_is_past (pc->check_contract.contract_terms-> + pay_deadline.abs_time)) { /* too late */ pay_end (pc, @@ -3257,7 +3307,7 @@ phase_check_contract (struct PayContext *pc) struct TMH_WireMethod *wm; wm = pc->hc->instance->wm_head; - while (0 != GNUNET_memcmp (&pc->contract_terms->h_wire, + while (0 != GNUNET_memcmp (&pc->check_contract.contract_terms->h_wire, &wm->h_wire)) wm = wm->next; if (NULL == wm) @@ -3271,7 +3321,7 @@ phase_check_contract (struct PayContext *pc) NULL)); return; } - pc->wm = wm; + pc->check_contract.wm = wm; } pc->phase = PP_VALIDATE_TOKENS; } @@ -3291,7 +3341,7 @@ phase_parse_wallet_data (struct PayContext *pc) struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_int64 ("choice_index", - &pc->choice_index), + &pc->parse_wallet_data.choice_index), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_array_const ("tokens_evs", @@ -3300,8 +3350,8 @@ phase_parse_wallet_data (struct PayContext *pc) GNUNET_JSON_spec_end () }; - pc->choice_index = -1; - if (NULL == pc->wallet_data) + pc->parse_wallet_data.choice_index = -1; + if (NULL == pc->parse_pay.wallet_data) { pc->phase = PP_CHECK_CONTRACT; return; @@ -3310,7 +3360,7 @@ phase_parse_wallet_data (struct PayContext *pc) enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (pc->connection, - pc->wallet_data, + pc->parse_pay.wallet_data, spec); if (GNUNET_YES != res) { @@ -3323,8 +3373,10 @@ phase_parse_wallet_data (struct PayContext *pc) } } - pc->token_envelopes_cnt = json_array_size (tokens_evs); - if (pc->token_envelopes_cnt > MAX_TOKEN_ALLOWED_OUTPUTs) + pc->parse_wallet_data.token_envelopes_cnt + = json_array_size (tokens_evs); + if (pc->parse_wallet_data.token_envelopes_cnt > + MAX_TOKEN_ALLOWED_OUTPUTs) { GNUNET_break_op (0); pay_end (pc, @@ -3335,15 +3387,9 @@ phase_parse_wallet_data (struct PayContext *pc) "'tokens_evs' array too long")); return; } - if (0 < pc->token_envelopes_cnt) - { - /* Calculate output commitment to be verified later. */ - TALER_json_hash (tokens_evs, - &pc->h_outputs); - } - - pc->token_envelopes = GNUNET_new_array (pc->token_envelopes_cnt, - struct TokenEnvelope); + pc->parse_wallet_data.token_envelopes + = GNUNET_new_array (pc->parse_wallet_data.token_envelopes_cnt, + struct TokenEnvelope); { unsigned int tokens_ev_index; @@ -3353,7 +3399,8 @@ phase_parse_wallet_data (struct PayContext *pc) tokens_ev_index, token_ev) { - struct TokenEnvelope *ev = &pc->token_envelopes[tokens_ev_index]; + struct TokenEnvelope *ev + = &pc->parse_wallet_data.token_envelopes[tokens_ev_index]; struct GNUNET_JSON_Specification ispec[] = { TALER_JSON_spec_token_envelope ("token_ev", &ev->blinded_token), @@ -3380,7 +3427,8 @@ phase_parse_wallet_data (struct PayContext *pc) { if (0 == GNUNET_memcmp (ev->blinded_token.blinded_pub, - pc->token_envelopes[j].blinded_token.blinded_pub)) + pc->parse_wallet_data.token_envelopes[j]. + blinded_token.blinded_pub)) { GNUNET_break_op (0); pay_end (pc, @@ -3395,8 +3443,8 @@ phase_parse_wallet_data (struct PayContext *pc) } } - TALER_json_hash (pc->wallet_data, - &pc->h_wallet_data); + TALER_json_hash (pc->parse_pay.wallet_data, + &pc->parse_wallet_data.h_wallet_data); pc->phase = PP_CHECK_CONTRACT; } @@ -3423,7 +3471,7 @@ phase_parse_pay (struct PayContext *pc) NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_object_const ("wallet_data", - &pc->wallet_data), + &pc->parse_pay.wallet_data), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_array_const ("tokens", @@ -3432,7 +3480,7 @@ phase_parse_pay (struct PayContext *pc) GNUNET_JSON_spec_end () }; - GNUNET_assert (PP_INIT == pc->phase); + GNUNET_assert (PP_PARSE_PAY == pc->phase); { enum GNUNET_GenericReturnValue res; @@ -3453,16 +3501,16 @@ phase_parse_pay (struct PayContext *pc) /* copy session ID (if set) */ if (NULL != session_id) { - pc->session_id = GNUNET_strdup (session_id); + pc->parse_pay.session_id = GNUNET_strdup (session_id); } else { /* use empty string as default if client didn't specify it */ - pc->session_id = GNUNET_strdup (""); + pc->parse_pay.session_id = GNUNET_strdup (""); } - pc->coins_cnt = json_array_size (coins); - if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS) + pc->parse_pay.coins_cnt = json_array_size (coins); + if (pc->parse_pay.coins_cnt > MAX_COIN_ALLOWED_COINS) { GNUNET_break_op (0); pay_end (pc, @@ -3474,8 +3522,8 @@ phase_parse_pay (struct PayContext *pc) return; } /* note: 1 coin = 1 deposit confirmation expected */ - pc->dc = GNUNET_new_array (pc->coins_cnt, - struct DepositConfirmation); + pc->parse_pay.dc = GNUNET_new_array (pc->parse_pay.coins_cnt, + struct DepositConfirmation); /* This loop populates the array 'dc' in 'pc' */ { @@ -3484,7 +3532,7 @@ phase_parse_pay (struct PayContext *pc) json_array_foreach (coins, coins_index, coin) { - struct DepositConfirmation *dc = &pc->dc[coins_index]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[coins_index]; const char *exchange_url; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_fixed_auto ("coin_sig", @@ -3536,7 +3584,7 @@ phase_parse_pay (struct PayContext *pc) { if (0 == GNUNET_memcmp (&dc->cdd.coin_pub, - &pc->dc[j].cdd.coin_pub)) + &pc->parse_pay.dc[j].cdd.coin_pub)) { GNUNET_break_op (0); pay_end (pc, @@ -3568,13 +3616,13 @@ phase_parse_pay (struct PayContext *pc) } /* Setup exchange group */ - for (unsigned int i = 0; i<pc->num_exchanges; i++) + for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++) { if (0 == - strcmp (pc->egs[i]->exchange_url, + strcmp (pc->parse_pay.egs[i]->exchange_url, exchange_url)) { - eg = pc->egs[i]; + eg = pc->parse_pay.egs[i]; break; } } @@ -3584,8 +3632,8 @@ phase_parse_pay (struct PayContext *pc) eg->pc = pc; eg->exchange_url = dc->exchange_url; eg->total = dc->cdd.amount; - GNUNET_array_append (pc->egs, - pc->num_exchanges, + GNUNET_array_append (pc->parse_pay.egs, + pc->parse_pay.num_exchanges, eg); } else @@ -3608,8 +3656,8 @@ phase_parse_pay (struct PayContext *pc) } } - pc->tokens_cnt = json_array_size (tokens); - if (pc->tokens_cnt > MAX_TOKEN_ALLOWED_INPUTs) + pc->parse_pay.tokens_cnt = json_array_size (tokens); + if (pc->parse_pay.tokens_cnt > MAX_TOKEN_ALLOWED_INPUTs) { GNUNET_break_op (0); pay_end (pc, @@ -3621,8 +3669,8 @@ phase_parse_pay (struct PayContext *pc) return; } - pc->tokens = GNUNET_new_array (pc->tokens_cnt, - struct TokenUseConfirmation); + pc->parse_pay.tokens = GNUNET_new_array (pc->parse_pay.tokens_cnt, + struct TokenUseConfirmation); /* This look populates the array 'tokens' in 'pc' */ { @@ -3631,7 +3679,7 @@ phase_parse_pay (struct PayContext *pc) json_array_foreach (tokens, tokens_index, token) { - struct TokenUseConfirmation *tuc = &pc->tokens[tokens_index]; + struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[tokens_index]; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_fixed_auto ("token_sig", &tuc->sig), @@ -3662,7 +3710,7 @@ phase_parse_pay (struct PayContext *pc) { if (0 == GNUNET_memcmp (&tuc->pub, - &pc->tokens[j].pub)) + &pc->parse_pay.tokens[j].pub)) { GNUNET_break_op (0); pay_end (pc, @@ -3690,48 +3738,48 @@ pay_context_cleanup (void *cls) { struct PayContext *pc = cls; - if (NULL != pc->timeout_task) + if (NULL != pc->batch_deposits.timeout_task) { - GNUNET_SCHEDULER_cancel (pc->timeout_task); - pc->timeout_task = NULL; + GNUNET_SCHEDULER_cancel (pc->batch_deposits.timeout_task); + pc->batch_deposits.timeout_task = NULL; } - if (NULL != pc->contract_terms_json) + if (NULL != pc->check_contract.contract_terms_json) { - json_decref (pc->contract_terms_json); - pc->contract_terms_json = NULL; + json_decref (pc->check_contract.contract_terms_json); + pc->check_contract.contract_terms_json = NULL; } - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (unsigned int i = 0; i<pc->parse_pay.coins_cnt; i++) { - struct DepositConfirmation *dc = &pc->dc[i]; + struct DepositConfirmation *dc = &pc->parse_pay.dc[i]; TALER_denom_sig_free (&dc->cdd.denom_sig); GNUNET_free (dc->exchange_url); } - GNUNET_free (pc->dc); - for (unsigned int i = 0; i<pc->num_exchanges; i++) + GNUNET_free (pc->parse_pay.dc); + for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++) { - struct ExchangeGroup *eg = pc->egs[i]; + struct ExchangeGroup *eg = pc->parse_pay.egs[i]; if (NULL != eg->fo) TMH_EXCHANGES_keys4exchange_cancel (eg->fo); GNUNET_free (eg); } - GNUNET_free (pc->egs); - if (NULL != pc->contract_terms) + GNUNET_free (pc->parse_pay.egs); + if (NULL != pc->check_contract.contract_terms) { - TALER_MERCHANT_contract_free (pc->contract_terms); - pc->contract_terms = NULL; + TALER_MERCHANT_contract_free (pc->check_contract.contract_terms); + pc->check_contract.contract_terms = NULL; } if (NULL != pc->response) { MHD_destroy_response (pc->response); pc->response = NULL; } - GNUNET_free (pc->session_id); + GNUNET_free (pc->parse_pay.session_id); GNUNET_CONTAINER_DLL_remove (pc_head, pc_tail, pc); - GNUNET_free (pc->pos_key); + GNUNET_free (pc->check_contract.pos_key); GNUNET_free (pc); } @@ -3763,7 +3811,7 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, (int) pc->phase); switch (pc->phase) { - case PP_INIT: + case PP_PARSE_PAY: phase_parse_pay (pc); break; case PP_PARSE_WALLET_DATA: diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -818,6 +818,9 @@ clean_order (void *cls) json_decref (mctf->description_i18n); switch (mctf->kind) { + case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID: + GNUNET_break (0); + break; case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION: for (size_t j = 0; j<mctf->details.subscription.trusted_domains_len; j++) GNUNET_free (mctf->details.subscription.trusted_domains[j]); @@ -1696,12 +1699,14 @@ add_output_token_family (struct OrderContext *oc, "lookup_token_family_key"); return GNUNET_SYSERR; case GNUNET_DB_STATUS_SOFT_ERROR: + /* Single-statement transaction shouldn't possibly cause serialization errors. + Thus treating like a hard error. */ GNUNET_break (0); reply_with_error (oc, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_SOFT_FAILURE, "lookup_token_family_key"); - return GNUNET_SYSERR; /* FIXME: retry instead? */ + return GNUNET_SYSERR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Output token family slug %s unknown\n", @@ -1861,12 +1866,14 @@ add_output_token_family (struct OrderContext *oc, NULL); return GNUNET_SYSERR; case GNUNET_DB_STATUS_SOFT_ERROR: + /* Single-statement transaction shouldn't possibly cause serialization errors. + Thus treating like a hard error. */ GNUNET_break (0); reply_with_error (oc, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_SOFT_FAILURE, NULL); - return GNUNET_SYSERR; // FIXME: or retry? + return GNUNET_SYSERR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); reply_with_error (oc, @@ -3003,11 +3010,10 @@ parse_order (struct OrderContext *oc) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - // FIXME: use CONFLICT and a different EC! reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - "no trusted exchange for this currency"); + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY, + oc->parse_order.details.v0.brutto.currency); return; } if (NULL != choices) @@ -3337,224 +3343,13 @@ parse_order (struct OrderContext *oc) static void parse_donau_instances (struct OrderContext *oc) { + /* FIXME-#9059: not yet implemented! */ return; } #endif -/** - * Parse the inputs for a particular choice. - * - * @param[in,out] oc order context - * @param[out] choice to parse inputs for - * @param jinputs array of inputs to parse - * @return #GNUNET_OK on success, #GNUNET_SYSERR - * if an error was encountered (and already handled) - */ -static enum GNUNET_GenericReturnValue -parse_order_inputs (struct OrderContext *oc, - struct TALER_MERCHANT_ContractChoice *choice, - const json_t *jinputs) -{ - const json_t *jinput; - size_t idx; - - json_array_foreach ((json_t *) jinputs, idx, jinput) - { - struct TALER_MERCHANT_ContractInput input = { - .details.token.count = 1 - }; - const char *kind; - const char *ierror_name; - unsigned int ierror_line; - struct GNUNET_JSON_Specification ispec[] = { - // FIXME: define spec parser for 'kind'... - GNUNET_JSON_spec_string ("kind", - &kind), - GNUNET_JSON_spec_string ("token_family_slug", - &input.details.token.token_family_slug), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("count", - &input.details.token.count), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (jinput, - ispec, - &ierror_name, - &ierror_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid input #%u for field %s\n", - (unsigned int) idx, - ierror_name); - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ierror_name); - return GNUNET_SYSERR; - } - - input.type = TMH_contract_input_type_from_string (kind); - if (TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID == input.type) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'kind' invalid in input #%u\n", - (unsigned int) idx); - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "kind"); - return GNUNET_SYSERR; - } - - if (0 == input.details.token.count) - { - /* Ignore inputs with 'number' field set to 0 */ - continue; - } - - if (GNUNET_OK != - add_input_token_family (oc, - input.details.token.token_family_slug)) - { - /* error is already scheduled, return. */ - return GNUNET_SYSERR; - } - - GNUNET_array_append (choice->inputs, - choice->inputs_len, - input); - } - return GNUNET_OK; -} - - -/** - * Parse the outputs for a particular choice. - * - * @param[in,out] oc order context - * @param[out] choice to parse inputs for - * @param joutputs array of outputs to parse - * @return #GNUNET_OK on success, #GNUNET_SYSERR - * if an error was encountered (and already handled) - */ -static enum GNUNET_GenericReturnValue -parse_order_outputs (struct OrderContext *oc, - struct TALER_MERCHANT_ContractChoice *choice, - const json_t *joutputs) -{ - const json_t *joutput; - size_t idx; - - json_array_foreach ((json_t *) joutputs, idx, joutput) - { - struct TALER_MERCHANT_ContractOutput output = { - .details.token.count = 1 - }; - const char *kind; - const char *ierror_name; - unsigned int ierror_line; - bool nots; - struct GNUNET_TIME_Timestamp valid_at; - struct GNUNET_JSON_Specification ispec[] = { - // FIXME: define spec parser for 'kind'... - GNUNET_JSON_spec_string ("kind", - &kind), - GNUNET_JSON_spec_string ("token_family_slug", - // FIXME... - (const char **) &output.details.token. - token_family_slug), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("count", - &output.details.token.count), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("valid_at", - &valid_at), - &nots), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (joutput, - ispec, - &ierror_name, - &ierror_line)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid output #%u for field %s\n", - (unsigned int) idx, - ierror_name); - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ierror_name); - return GNUNET_SYSERR; - } - if (nots) - { - valid_at = oc->parse_order.pay_deadline; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Looking for output token valid at pay deadline %s\n", - GNUNET_TIME_timestamp2s (valid_at)); - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Looking for output token valid at %s\n", - GNUNET_TIME_timestamp2s (valid_at)); - } - if (GNUNET_TIME_timestamp_cmp (valid_at, - <, - oc->parse_order.pay_deadline)) - { - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "valid_at before pay_deadline"); - return GNUNET_SYSERR; - } - - output.type = TMH_contract_output_type_from_string (kind); - if (TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID == output.type) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Field 'kind' invalid in output #%u\n", - (unsigned int) idx); - reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "kind"); - return GNUNET_SYSERR; - } - - if (0 == output.details.token.count) - { - /* Ignore outputs with 'number' field set to 0. */ - continue; - } - - if (GNUNET_OK != - add_output_token_family (oc, - output.details.token.token_family_slug, - valid_at, - &output.details.token.key_index)) - { - /* Error is already scheduled, return. */ - return GNUNET_SYSERR; - } - - GNUNET_array_append (choice->outputs, - choice->outputs_len, - output); - } - return GNUNET_OK; -} - /** * Parse contract choices. Upon success, continue @@ -3652,11 +3447,10 @@ parse_choices (struct OrderContext *oc) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - // FIXME: use CONFLICT and a different EC! reply_with_error (oc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - "no trusted exchange for this currency"); + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY, + choice->amount.currency); return; } @@ -3744,16 +3538,21 @@ parse_choices (struct OrderContext *oc) case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: GNUNET_assert (0); break; + case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT: + GNUNET_break (0); /* FIXME-#9059: not yet implemented! */ + break; case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: /* Ignore inputs tokens with 'count' field set to 0 */ if (0 == output.details.token.count) continue; - // FIXME: no valid_at in choice->output, is NOW fine? + if (0 == output.details.token.valid_at.abs_time.abs_value_us) + output.details.token.valid_at + = GNUNET_TIME_timestamp_get (); if (GNUNET_OK != add_output_token_family (oc, output.details.token.token_family_slug, - GNUNET_TIME_timestamp_get (), + output.details.token.valid_at, &output.details.token.key_index)) { diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.c b/src/backend/taler-merchant-httpd_private-post-token-families.c @@ -46,6 +46,10 @@ static bool token_families_equal (const struct TALER_MERCHANTDB_TokenFamilyDetails *tf1, const struct TALER_MERCHANTDB_TokenFamilyDetails *tf2) { + /* Note: we're not comparing 'cipher', as that is selected + in the database to some default value and we currently + do not allow the SPA to change it. As a result, it should + always be "NULL" in tf1 and the DB-default in tf2. */ return ( (0 == strcmp (tf1->slug, tf2->slug)) && (0 == strcmp (tf1->name, diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c @@ -579,6 +579,7 @@ exchange_check_cb ( EXCHANGE_TIMEOUT); i->last_kyc_check = GNUNET_TIME_timestamp_get (); i->due = GNUNET_TIME_relative_to_absolute (i->backoff); + i->auth_ok = false; break; } diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c @@ -79,6 +79,8 @@ TALER_MERCHANTDB_token_family_details_free ( GNUNET_free (tf->name); GNUNET_free (tf->description); json_decref (tf->description_i18n); + json_decref (tf->extra_data); + GNUNET_free (tf->cipher_spec); } diff --git a/src/backenddb/pg_get_kyc_status.c b/src/backenddb/pg_get_kyc_status.c @@ -50,11 +50,12 @@ TMH_PG_get_kyc_status ( }; uint32_t h32 = 0; uint32_t e32 = 0; + bool token_is_null = true; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_auto_from_type ("access_token", access_token), - auth_ok), + &token_is_null), GNUNET_PQ_result_spec_uint32 ("exchange_http_status", &h32), GNUNET_PQ_result_spec_uint32 ("exchange_ec_code", @@ -101,5 +102,6 @@ TMH_PG_get_kyc_status ( rs); *last_ec = (enum TALER_ErrorCode) (int) e32; *last_http_status = (unsigned int) h32; + *auth_ok = ! token_is_null; return qs; } diff --git a/src/backenddb/pg_lookup_token_family.c b/src/backenddb/pg_lookup_token_family.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2025 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -60,6 +60,8 @@ TMH_PG_lookup_token_family ( &details->slug), GNUNET_PQ_result_spec_string ("name", &details->name), + GNUNET_PQ_result_spec_string ("cipher_choice", + &details->cipher_spec), GNUNET_PQ_result_spec_string ("description", &details->description), TALER_PQ_result_spec_json ("description_i18n", @@ -94,6 +96,7 @@ TMH_PG_lookup_token_family ( "SELECT" " slug" ",name" + ",cipher_choice" ",description" ",description_i18n" ",extra_data" diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -852,7 +852,7 @@ TALER_TESTING_cmd_merchant_get_order ( * @param wired whether the order has been wired or not. * @param transfers NULL-terminated list of labels (const char *) of * wire transfers (commands) we expect to be aggregated in the order - * (assuming @a http_code is #MHD_HTTP_OK). If @e paid is fale, this + * (assuming @a http_code is #MHD_HTTP_OK). If @e paid is false, this * parameter is ignored. * @param refunded whether the order has been refunded. * @param refunds NULL-terminated list of labels (const char *) of diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h @@ -256,8 +256,12 @@ struct TALER_MERCHANT_ContractOutput */ unsigned int count; - // FIXME: add support for clients picking a validity - // period in the future for output tokens! + /** + * Determines when the output token should be valid. + * Optional, set to zero for not specified (then we + * use the current time). + */ + struct GNUNET_TIME_Timestamp valid_at; } token; @@ -669,6 +673,19 @@ TALER_MERCHANT_contract_parse (json_t *input, /** + * Provide specification to parse an JSON contract input type. + * The value is provided as a descriptive string. + * + * @param name name of the JSON member with the contract type + * @param[out] cit where to store the contract input type + * @return spec for parsing a contract input type + */ +struct GNUNET_JSON_Specification +TALER_MERCHANT_json_spec_cit (const char *name, + enum TALER_MERCHANT_ContractInputType *cit); + + +/** * Parse JSON contract terms choice input. * * @param[in] root JSON object containing choice input @@ -686,6 +703,19 @@ TALER_MERCHANT_parse_choice_input ( /** + * Provide specification to parse an JSON contract output type. + * The value is provided as a descriptive string. + * + * @param name name of the JSON member with the contract type + * @param[out] cot where to store the contract output type + * @return spec for parsing a contract output type + */ +struct GNUNET_JSON_Specification +TALER_MERCHANT_json_spec_cot (const char *name, + enum TALER_MERCHANT_ContractOutputType *cot); + + +/** * Parse JSON contract terms choice output. * * @param[in] root JSON object containing choice output diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -229,7 +229,7 @@ run (void *cls, TALER_TESTING_cmd_merchant_post_orders_no_claim ( "create-proposal-bad-currency", merchant_url, - MHD_HTTP_BAD_REQUEST, + MHD_HTTP_CONFLICT, "4", GNUNET_TIME_UNIT_ZERO_TS, GNUNET_TIME_UNIT_FOREVER_TS, diff --git a/src/testing/test_merchant_order_creation.sh b/src/testing/test_merchant_order_creation.sh @@ -251,7 +251,7 @@ echo "OK" echo -n "Creating discount token family..." VALID_AFTER="{\"t_s\": $(date +%s)}" # now VALID_BEFORE="{\"t_s\": $(date +%s -d "+30 days")}" # 30 days from now -DURATION="{\"d_us\": $(echo '30 * 24 * 60 * 60 * 1000000' | bc)}" # 30 days +DURATION="{\"d_us\": $(expr 30 \* 24 \* 60 \* 60 \* 1000000)}" # 30 days STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \ -d "{\"kind\": \"discount\", \"slug\":\"test-discount\", \"name\": \"Test discount\", \"description\": \"Less money $$\", \"description_i18n\": {\"en\": \"Less money $$\", \"es\": \"Menos dinero $$\"}, \"valid_after\": $VALID_AFTER, \"valid_before\": $VALID_BEFORE, \"duration\": $DURATION, \"validity_granularity\": $DURATION}" \ -w "%{http_code}" -s -o /dev/null) @@ -267,7 +267,7 @@ echo "Ok" echo -n "Creating subscription token family..." VALID_AFTER="{\"t_s\": $(date +%s)}" # now VALID_BEFORE="{\"t_s\": $(date +%s -d "+30 days")}" # 30 days from now -DURATION="{\"d_us\": $(echo '30 * 24 * 60 * 60 * 1000000' | bc)}" # 30 days +DURATION="{\"d_us\": $(expr 30 \* 24 \* 60 \* 60 \* 1000000)}" # 30 days STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \ -d "{\"kind\": \"subscription\", \"slug\":\"test-subscription\", \"name\": \"Test subscription\", \"description\": \"Money per month\", \"description_i18n\": {\"en\": \"Money $$$ per month\", \"es\": \"Dinero $$$ al mes\"}, \"valid_after\": $VALID_AFTER, \"valid_before\": $VALID_BEFORE, \"duration\": $DURATION, \"validity_granularity\": $DURATION}" \ -w "%{http_code}" -s -o /dev/null) diff --git a/src/util/.gitignore b/src/util/.gitignore @@ -1 +1,2 @@ taler-merchant-config +test_contract diff --git a/src/util/Makefile.am b/src/util/Makefile.am @@ -41,6 +41,7 @@ lib_LTLIBRARIES = \ libtalermerchantutil_la_SOURCES = \ contract_parse.c \ contract_serialize.c \ + json.c \ os_installation.c libtalermerchantutil_la_LIBADD = \ -lgnunetjson \ @@ -49,7 +50,7 @@ libtalermerchantutil_la_LIBADD = \ -ltalerutil \ $(XLIB) libtalermerchantutil_la_LDFLAGS = \ - -version-info 0:0:0 \ + -version-info 1:0:1 \ -export-dynamic -no-undefined test_contract_SOURCES = \ diff --git a/src/util/contract_parse.c b/src/util/contract_parse.c @@ -112,42 +112,6 @@ spec_merchant_details (const char *name, /** - * Get enum value from contract input type string. - * - * @param str contract input type string - * @return enum value of input type - */ -static enum TALER_MERCHANT_ContractInputType -contract_input_type_from_string (const char *str) -{ - /* For now, only 'token' is the only supported option. */ - if (0 == strcmp ("token", - str)) - return TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN; - return TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID; -} - - -/** - * Get enum value from contract output type string. - * - * @param str contract output type string - * @return enum value of output type - */ -static enum TALER_MERCHANT_ContractOutputType -contract_output_type_from_string (const char *str) -{ - if (0 == strcmp ("token", - str)) - return TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN; - if (0 == strcmp ("tax-receipt", - str)) - return TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT; - return TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID; -} - - -/** * Get enum value from contract token type string. * * @param str contract token type string @@ -173,12 +137,11 @@ TALER_MERCHANT_parse_choice_input ( size_t index, bool order) { - const char *type; const char *ename; unsigned int eline; struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("type", - &type), + TALER_MERCHANT_json_spec_cit ("type", + &input->type), GNUNET_JSON_spec_end () }; @@ -197,11 +160,10 @@ TALER_MERCHANT_parse_choice_input ( return GNUNET_SYSERR; } - input->type = contract_input_type_from_string (type); - switch (input->type) { case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID: + GNUNET_break (0); break; case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN: { @@ -249,12 +211,11 @@ TALER_MERCHANT_parse_choice_output ( size_t index, bool order) { - const char *type; const char *ename; unsigned int eline; struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("type", - &type), + TALER_MERCHANT_json_spec_cot ("type", + &output->type), GNUNET_JSON_spec_end () }; @@ -273,11 +234,10 @@ TALER_MERCHANT_parse_choice_output ( return GNUNET_SYSERR; } - output->type = contract_output_type_from_string (type); - switch (output->type) { case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID: + GNUNET_break (0); break; case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN: { @@ -288,6 +248,10 @@ TALER_MERCHANT_parse_choice_output ( GNUNET_JSON_spec_uint ("count", &output->details.token.count), NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("valid_at", + &output->details.token.valid_at), + NULL), (! order) ? GNUNET_JSON_spec_uint ("key_index", &output->details.token.key_index) @@ -995,8 +959,16 @@ parse_contract_version (void *cls, } -struct GNUNET_JSON_Specification -TALER_MERCHANT_JSON_spec_contract_version ( +/** + * Create JSON specification to parse a merchant contract + * version. + * + * @param name name of the field + * @param[out] version where to write the contract version + * @return JSON specification object + */ +static struct GNUNET_JSON_Specification +spec_contract_version ( const char *name, enum TALER_MERCHANT_ContractVersion *version) { @@ -1018,7 +990,7 @@ TALER_MERCHANT_JSON_spec_contract_version ( * @param[out] contract where to write the data * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error */ -enum GNUNET_GenericReturnValue +static enum GNUNET_GenericReturnValue parse_contract_v0 ( json_t *input, struct TALER_MERCHANT_Contract *contract) @@ -1068,7 +1040,7 @@ parse_contract_v0 ( * @param[out] contract where to write the data * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error */ -enum GNUNET_GenericReturnValue +static enum GNUNET_GenericReturnValue parse_contract_v1 ( json_t *input, struct TALER_MERCHANT_Contract *contract) @@ -1110,12 +1082,11 @@ struct TALER_MERCHANT_Contract * TALER_MERCHANT_contract_parse (json_t *input, bool nonce_optional) { - struct TALER_MERCHANT_Contract *contract; - contract = GNUNET_new (struct TALER_MERCHANT_Contract); - + struct TALER_MERCHANT_Contract *contract + = GNUNET_new (struct TALER_MERCHANT_Contract); struct GNUNET_JSON_Specification espec[] = { - TALER_MERCHANT_JSON_spec_contract_version ("version", - &contract->version), + spec_contract_version ("version", + &contract->version), GNUNET_JSON_spec_string_copy ("summary", &contract->summary), /* FIXME: do i18n_str validation in the future */ diff --git a/src/util/json.c b/src/util/json.c @@ -0,0 +1,171 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file util/json.c + * @brief helper functions to parse JSON + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_common.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> +#include "taler_merchant_util.h" + + +/** + * Parse given JSON object to `enum TALER_MERCHANT_ContractInputType` + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_contract_input_type (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + static const struct Entry + { + const char *name; + enum TALER_MERCHANT_ContractInputType val; + } lt [] = { +#if FUTURE + { .name = "coin", + .val = TALER_MERCHANT_CONTRACT_INPUT_TYPE_COIN }, +#endif + { .name = "token", + .val = TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN }, + { .name = NULL, + .val = TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID } + }; + enum TALER_MERCHANT_ContractInputType *res + = (enum TALER_MERCHANT_ContractInputType *) spec->ptr; + + (void) cls; + if (json_is_string (root)) + { + const char *str; + + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; NULL != lt[i].name; i++) + { + if (0 == strcasecmp (str, + lt[i].name)) + { + *res = lt[i].val; + return GNUNET_OK; + } + } + } + GNUNET_break_op (0); + return GNUNET_SYSERR; +} + + +struct GNUNET_JSON_Specification +TALER_MERCHANT_json_spec_cit (const char *name, + enum TALER_MERCHANT_ContractInputType *cit) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_contract_input_type, + .field = name, + .ptr = cit + }; + + *cit = TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID; + return ret; +} + + +/** + * Parse given JSON object to `enum TALER_MERCHANT_ContractOutputType` + * + * @param cls closure, NULL + * @param root the json object representing data + * @param[out] spec where to write the data + * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error + */ +static enum GNUNET_GenericReturnValue +parse_contract_output_type (void *cls, + json_t *root, + struct GNUNET_JSON_Specification *spec) +{ + static const struct Entry + { + const char *name; + enum TALER_MERCHANT_ContractOutputType val; + } lt [] = { + { .name = "token", + .val = TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN }, + { .name = "tax-receipt", + .val = TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT }, +#if FUTURE + { .name = "coin", + .val = TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN }, +#endif + { .name = NULL, + .val = TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID } + }; + enum TALER_MERCHANT_ContractOutputType *res + = (enum TALER_MERCHANT_ContractOutputType *) spec->ptr; + + (void) cls; + if (json_is_string (root)) + { + const char *str; + + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; NULL != lt[i].name; i++) + { + if (0 == strcasecmp (str, + lt[i].name)) + { + *res = lt[i].val; + return GNUNET_OK; + } + } + } + GNUNET_break_op (0); + return GNUNET_SYSERR; +} + + +struct GNUNET_JSON_Specification +TALER_MERCHANT_json_spec_cot (const char *name, + enum TALER_MERCHANT_ContractOutputType *cot) +{ + struct GNUNET_JSON_Specification ret = { + .parser = &parse_contract_output_type, + .field = name, + .ptr = cot + }; + + *cot = TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID; + return ret; +}