aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/Makefile.am1
-rw-r--r--src/backend/taler-merchant-httpd_contract.c47
-rw-r--r--src/backend/taler-merchant-httpd_contract.h592
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c720
-rw-r--r--src/backenddb/Makefile.am2
-rw-r--r--src/backenddb/pg_insert_token_family.h2
-rw-r--r--src/backenddb/pg_insert_token_family_key.c95
-rw-r--r--src/backenddb/pg_insert_token_family_key.h46
-rw-r--r--src/backenddb/pg_lookup_token_family.c4
-rw-r--r--src/backenddb/pg_lookup_token_family_key.c161
-rw-r--r--src/backenddb/pg_lookup_token_family_key.h50
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c7
-rw-r--r--src/include/taler_merchantdb_plugin.h59
13 files changed, 1728 insertions, 58 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index d521608d..dbc7cde8 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -25,6 +25,7 @@ bin_PROGRAMS = \
taler_merchant_httpd_SOURCES = \
taler-merchant-httpd.c taler-merchant-httpd.h \
taler-merchant-httpd_config.c taler-merchant-httpd_config.h \
+ taler-merchant-httpd_contract.c taler-merchant-httpd_contract.h \
taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \
taler-merchant-httpd_get-orders-ID.c \
taler-merchant-httpd_get-orders-ID.h \
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
new file mode 100644
index 00000000..38c82e70
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_contract.c
+ * @brief shared logic for contract terms handling
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "taler-merchant-httpd_contract.h"
+
+enum TALER_MerchantContractInputType
+TMH_string_to_contract_input_type (const char *str)
+{
+ /* For now, only 'token' is the only supported option. */
+ if (0 == strcmp("token", str))
+ {
+ return TALER_MCIT_TOKEN;
+ }
+
+ return TALER_MCIT_INVALID;
+}
+
+enum TALER_MerchantContractOutputType
+TMH_string_to_contract_output_type (const char *str)
+{
+ /* For now, only 'token' is the only supported option. */
+ if (0 == strcmp("token", str))
+ {
+ return TALER_MCOT_TOKEN;
+ }
+
+ return TALER_MCOT_INVALID;
+}
diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h
new file mode 100644
index 00000000..defe6479
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -0,0 +1,592 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_contract.h
+ * @brief shared logic for contract terms handling
+ * @author Christian Blättler
+ */
+#include "taler-merchant-httpd.h"
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+
+
+/**
+ * Possible versions of the contract terms.
+ */
+enum TALER_MerchantContractVersion
+{
+
+ /**
+ * Version 0
+ */
+ TALER_MCV_V0 = 0,
+
+ /**
+ * Version 1
+ */
+ TALER_MCV_V1 = 1
+};
+
+/**
+ * Possible token kinds.
+ */
+enum TALER_MerchantContractTokenKind
+{
+
+ /**
+ * Subscription token kind
+ */
+ TALER_MCTK_SUBSCRIPTION = 0,
+
+ /**
+ * Discount token kind
+ */
+ TALER_MCTK_DISCOUNT = 1
+};
+
+/**
+ * Possible input types for the contract terms.
+ */
+enum TALER_MerchantContractInputType
+{
+
+ /**
+ * Input type invalid
+ */
+ TALER_MCIT_INVALID = 0,
+
+ /**
+ * Input type coin
+ */
+ TALER_MCIT_COIN = 1,
+
+ /**
+ * Input type token
+ */
+ TALER_MCIT_TOKEN = 2
+};
+
+/**
+ * Contract input (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractInput
+{
+ /**
+ * Type of the input.
+ */
+ enum TALER_MerchantContractInputType type;
+
+ union
+ {
+ /**
+ * Coin-based input (ration). (Future work, only here for reference)
+ */
+ // struct
+ // {
+ // /**
+ // * Price to be paid.
+ // */
+ // struct TALER_Amount price;
+
+ // /**
+ // * Base URL of the ration authority.
+ // */
+ // const char *ration_authority_url;
+ // } coin;
+
+ /**
+ * Token-based input.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be used.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Start time of the validity period of the token. Base on this timestamp
+ * the wallet can find the correct key for this token in token_authorities.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
+};
+
+/**
+ * Possible output types for the contract terms.
+ */
+enum TALER_MerchantContractOutputType
+{
+
+ /**
+ * Invalid output type
+ */
+ TALER_MCOT_INVALID = 0,
+
+ /**
+ * Output type coin
+ */
+ TALER_MCOT_COIN = 1,
+
+ /**
+ * Output type token
+ */
+ TALER_MCOT_TOKEN = 2,
+
+ /**
+ * Output type tax-receipt
+ */
+ TALER_MCOT_TAX_RECEIPT = 3
+
+};
+
+/**
+ * Contract output (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractOutput
+{
+ /**
+ * Type of the output.
+ */
+ enum TALER_MerchantContractOutputType type;
+
+ union
+ {
+ /**
+ * Coin-based output.
+ */
+ struct {
+ /**
+ * Coins that will be yielded. This excludes any applicable withdraw fees.
+ */
+ struct TALER_Amount brutto_yield;
+
+ /**
+ * Base URL of the exchange that will issue the coins.
+ */
+ const char *exchange_url;
+ } coin;
+
+ /**
+ * Tax-receipt output.
+ */
+ struct
+ {
+ /**
+ * Base URL of the donation authority that will issue the tax receipt.
+ */
+ const char *donau_url;
+ } tax_receipt;
+
+ /**
+ * Token-based output.
+ */
+ struct
+ {
+ /**
+ * Slug of the token family to be issued.
+ */
+ const char *token_family_slug;
+
+ /**
+ * Start time of the validity period of the token. Base on this timestamp
+ * the wallet can find the correct key for this token in token_authorities.
+ */
+ struct GNUNET_TIME_Timestamp valid_after;
+
+ /**
+ * Number of tokens of this type required. Defaults to one if the
+ * field is not provided.
+ */
+ unsigned int count;
+ } token;
+ } details;
+};
+
+/**
+ * Contract choice (part of the v1 contract terms).
+ */
+struct TALER_MerchantContractChoice
+{
+ /**
+ * List of inputs the wallet must provision (all of them) to satisfy the
+ * conditions for the contract.
+ */
+ struct TALER_MerchantContractInput *inputs;
+
+ /**
+ * Length of the @e inputs array.
+ */
+ unsigned int inputs_len;
+
+ /**
+ * List of outputs the merchant promises to yield (all of them) once
+ * the contract is paid.
+ */
+ struct TALER_MerchantContractOutput *outputs;
+
+ /**
+ * Length of the @e outputs array.
+ */
+ unsigned int outputs_len;
+};
+
+struct TALER_MerchantContractLimits
+{
+ /**
+ * Currency these limits are for.
+ */
+ char currency[TALER_CURRENCY_LEN];
+
+ /**
+ * The hash of the merchant instance's wire details.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Wire transfer method identifier for the wire method associated with ``h_wire``.
+ * The wallet may only select exchanges via a matching auditor if the
+ * exchange also supports this wire method.
+ * The wire transfer fees must be added based on this wire transfer method.
+ */
+ char *wire_method;
+
+ /**
+ * Maximum total deposit fee accepted by the merchant for this contract.
+ */
+ struct TALER_Amount max_fee;
+};
+
+struct TALER_MerchantContractTokenAuthority
+{
+ /**
+ * Label of the token authority.
+ */
+ const char *label;
+
+ /**
+ * Human-readable description of the semantics of the tokens issued by
+ * this authority.
+ */
+ char *description;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized description.
+ */
+ json_t *description_i18n;
+
+ /**
+ * Public key used to validate tokens signed by this authority.
+ */
+ struct TALER_TokenFamilyPublicKey *pub;
+
+ /**
+ * When will tokens signed by this key expire?
+ */
+ struct GNUNET_TIME_Timestamp token_expiration;
+
+ /**
+ * Must a wallet understand this token type to process contracts that
+ * consume or yield it?
+ */
+ bool critical;
+
+ /**
+ * Kind of the token.
+ */
+ enum TALER_MerchantContractTokenKind kind;
+
+ /**
+ * Kind-specific information about the token.
+ */
+ union
+ {
+ /**
+ * Subscription token.
+ */
+ struct
+ {
+ /**
+ * When does the subscription period start?
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * When does the subscription period end?
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * Array of domain names where this subscription can be safely used
+ * (e.g. the issuer warrants that these sites will re-issue tokens of
+ * this type if the respective contract says so). May contain "*" for
+ * any domain or subdomain.
+ */
+ const char **trusted_domains;
+
+ /**
+ * Length of the @e trusted_domains array.
+ */
+ unsigned int trusted_domains_len;
+ } subscription;
+
+ /**
+ * Discount token.
+ */
+ struct
+ {
+ /**
+ * Array of domain names where this discount token is intended to be
+ * used. May contain "*" for any domain or subdomain. Users should be
+ * warned about sites proposing to consume discount tokens of this
+ * type that are not in this list that the merchant is accepting a
+ * coupon from a competitor and thus may be attaching different
+ * semantics (like get 20% discount for my competitors 30% discount
+ * token).
+ */
+ const char **expected_domains;
+
+ /**
+ * Length of the @e expected_domains array.
+ */
+ unsigned int expected_domains_len;
+
+ } discount;
+ } details;
+};
+
+/**
+ * Struct to hold contract terms in v0 and v1 format. v0 contracts are mdoelled
+ * as a v1 contract with a single choice and no inputs and outputs. Use the
+ * version field to explicitly differentiate between v0 and v1 contracts.
+ */
+struct TALER_MerchantContract
+{
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Our order ID.
+ */
+ const char *order_id;
+
+ /**
+ * Merchant base URL.
+ */
+ char *merchant_base_url;
+
+ /**
+ * Merchant information.
+ */
+ struct
+ {
+ /**
+ * Legal name of the instance
+ */
+ char *name;
+
+ /**
+ * Merchant's site url
+ */
+ char *website;
+
+ /**
+ * Email contact for customers
+ */
+ char *email;
+
+ /**
+ * merchant's logo data uri
+ */
+ char *logo;
+
+ /**
+ * Merchant address
+ */
+ json_t *address;
+
+ /**
+ * Jurisdiction of the business
+ */
+ json_t *jurisdiction;
+ } merchant;
+
+ /**
+ * Price to be paid for the transaction. Could be 0. The price is in addition
+ * to other instruments, such as rations and tokens.
+ * The exchange will subtract deposit fees from that amount
+ * before transferring it to the merchant.
+ */
+ struct TALER_Amount brutto;
+
+ /**
+ * Summary of the contract.
+ */
+ const char *summary;
+
+ /**
+ * Internationalized summary.
+ */
+ json_t *summary_i18n;
+
+ /**
+ * URL that will show that the contract was successful
+ * after it has been paid for.
+ */
+ const char *fulfillment_url;
+
+ /**
+ * Message shown to the customer after paying for the contract.
+ * Either fulfillment_url or fulfillment_message must be specified.
+ */
+ const char *fulfillment_message;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized fulfillment messages.
+ */
+ json_t *fulfillment_message_i18n;
+
+ /**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * Timestamp of the contract.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Deadline for refunds.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Specifies for how long the wallet should try to get an
+ * automatic refund for the purchase.
+ */
+ struct GNUNET_TIME_Relative auto_refund;
+
+ /**
+ * Payment deadline.
+ */
+ struct GNUNET_TIME_Timestamp pay_deadline;
+
+ /**
+ * Wire transfer deadline.
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
+
+ /**
+ * Delivery date.
+ */
+ struct GNUNET_TIME_Timestamp delivery_date;
+
+ /**
+ * Delivery location.
+ */
+ json_t *delivery_location;
+
+ /**
+ * Nonce generated by the wallet and echoed by the merchant
+ * in this field when the proposal is generated.
+ */
+ const char *nonce;
+
+ /**
+ * Extra data that is only interpreted by the merchant frontend.
+ */
+ const json_t *extra;
+
+ /**
+ * Specified version of the contract.
+ */
+ enum TALER_MerchantContractVersion version;
+
+ /**
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Array of token authorities.
+ */
+ struct TALER_MerchantContractTokenAuthority *token_authorities;
+
+ /**
+ * Length of the @e token_authorities array.
+ */
+ unsigned int token_authorities_len;
+
+ /**
+ * Maximum fee as given by the client request.
+ */
+ struct TALER_Amount max_fee;
+
+ // TODO: Add exchanges array
+};
+
+enum TALER_MerchantContractInputType
+TMH_string_to_contract_input_type (const char *str);
+
+enum TALER_MerchantContractOutputType
+TMH_string_to_contract_output_type (const char *str);
+
+/**
+ * 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_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out);
+
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract_v0 (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out);
+
+enum GNUNET_GenericReturnValue
+TMH_serialize_contract_v1 (const struct TALER_MerchantContract *contract,
+ const struct TMH_MerchantInstance *instance,
+ json_t *exchanges,
+ json_t **out); \ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index 7ca56319..b2070bcc 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -22,19 +22,25 @@
* @brief the POST /orders handler
* @author Christian Grothoff
* @author Marcello Stanisci
+ * @author Christian Blättler
*/
#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
+#include <microhttpd.h>
#include <string.h>
+#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler_merchantdb_plugin.h"
/**
@@ -231,43 +237,58 @@ struct OrderContext
struct
{
/**
+ * Version of the contract terms.
+ */
+ enum TALER_MerchantContractVersion version;
+
+ /**
* Our order ID.
*/
const char *order_id;
/**
- * Summary of the order.
- */
+ * Summary of the contract.
+ */
const char *summary;
/**
- * Internationalized summary.
- */
+ * Internationalized summary.
+ */
json_t *summary_i18n;
/**
- * URL where the same contract could be ordered again (if available).
- */
- const char *public_reorder_url;
-
- /**
- * URL that will show that the order was successful
- * after it has been paid for.
- */
+ * URL that will show that the contract was successful
+ * after it has been paid for.
+ */
const char *fulfillment_url;
/**
- * Message shown to the customer after paying for the order.
- * Either fulfillment_url or fulfillment_message must be specified.
- */
+ * Message shown to the customer after paying for the contract.
+ * Either fulfillment_url or fulfillment_message must be specified.
+ */
const char *fulfillment_message;
/**
- * Map from IETF BCP 47 language tags to localized fulfillment messages.
- */
+ * Map from IETF BCP 47 language tags to localized fulfillment messages.
+ */
json_t *fulfillment_message_i18n;
/**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Array of contract choices. Is null for v0 contracts.
+ */
+ const json_t *choices;
+
+ /**
* Merchant base URL.
*/
char *merchant_base_url;
@@ -303,14 +324,9 @@ struct OrderContext
json_t *delivery_location;
/**
- * Array of products that are part of the purchase.
- */
- const json_t *products;
-
- /**
- * Gross amount value of the contract. Used to
- * compute @e max_stefan_fee.
- */
+ * Gross amount value of the contract. Used to
+ * compute @e max_stefan_fee.
+ */
struct TALER_Amount brutto;
/**
@@ -343,6 +359,34 @@ struct OrderContext
} parse_order;
/**
+ * Information set in the ORDER_PHASE_PARSE_CHOICES phase.
+ */
+ struct
+ {
+ /**
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Array of token types referenced in the contract.
+ */
+ struct TALER_MerchantContractTokenAuthority *authorities;
+
+ /**
+ * Length of the @e authorities array.
+ */
+ unsigned int authorities_len;
+ } parse_choices;
+
+ /**
* Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
*/
struct
@@ -477,6 +521,7 @@ struct OrderContext
{
ORDER_PHASE_PARSE_REQUEST,
ORDER_PHASE_PARSE_ORDER,
+ ORDER_PHASE_PARSE_CHOICES,
ORDER_PHASE_MERGE_INVENTORY,
ORDER_PHASE_ADD_PAYMENT_DETAILS,
ORDER_PHASE_SET_EXCHANGES,
@@ -634,6 +679,16 @@ clean_order (void *cls)
json_decref (oc->merge_inventory.products);
oc->merge_inventory.products = NULL;
}
+ // TODO: Check if this is even correct
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ GNUNET_array_grow (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ 0);
+ GNUNET_array_grow (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ 0);
+ }
GNUNET_array_grow (oc->parse_request.inventory_products,
oc->parse_request.inventory_products_length,
0);
@@ -1262,6 +1317,145 @@ get_exchange_keys (void *cls,
rx);
}
+/**
+ * Fetch details about the token family with the given @a slug
+ * and add them to the list of token authorities. Check if the
+ * token family already has a valid key configured and if not,
+ * create a new one.
+ *
+ * @param[in,out] oc order context
+ * @param slug slug of the token family
+ * @param start_date validity start date of the token
+ */
+static MHD_RESULT
+set_token_authority (struct OrderContext *oc,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp start_date)
+{
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
+ struct TALER_MerchantContractTokenAuthority authority;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute min_start_date = GNUNET_TIME_absolute_subtract (
+ start_date.abs_time,
+ // TODO: make this configurable. This is the granularity of token
+ // expiration dates.
+ GNUNET_TIME_UNIT_DAYS
+ );
+
+ qs = TMH_db->lookup_token_family_key (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ slug,
+ GNUNET_TIME_absolute_to_timestamp (min_start_date),
+ start_date,
+ &key_details);
+
+ if (qs <= 0)
+ {
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ unsigned int http_status = 0;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Token family slug unknown\n");
+ http_status = MHD_HTTP_NOT_FOUND;
+ /* TODO: Add specific error code */
+ ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+ GNUNET_break (0);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ "authority_label");
+ return MHD_NO;
+ }
+
+ if (NULL == key_details.pub)
+ {
+ /* If public key is NULL, private key must also be NULL */
+ GNUNET_assert (NULL == key_details.priv);
+
+ struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Timestamp valid_before = GNUNET_TIME_absolute_to_timestamp(
+ GNUNET_TIME_absolute_add (now,
+ key_details.token_family.duration));
+
+ // TODO: Should I add a wrapper around this, so it directly accepts structs of type
+ // TALER_TokenFamilyPublicKey and TALER_TokenFamilyPrivateKey?
+ GNUNET_CRYPTO_blind_sign_keys_create (&priv,
+ &pub,
+ // TODO: Make cipher and key length configurable
+ GNUNET_CRYPTO_BSA_RSA,
+ 4096);
+
+ struct TALER_TokenFamilyPublicKey token_pub = {
+ .public_key = *pub,
+ };
+ struct TALER_TokenFamilyPrivateKey token_priv = {
+ .private_key = *priv,
+ };
+
+ qs = TMH_db->insert_token_family_key (TMH_db->cls,
+ slug,
+ &token_pub,
+ &token_priv,
+ GNUNET_TIME_absolute_to_timestamp (now),
+ valid_before);
+
+ authority.token_expiration = valid_before;
+ authority.pub = &token_pub;
+ // GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key);
+ } else {
+ authority.token_expiration = key_details.valid_before;
+ authority.pub = key_details.pub;
+ }
+
+ authority.label = slug;
+ authority.description = key_details.token_family.description;
+ authority.description_i18n = key_details.token_family.description_i18n;
+
+ GNUNET_free (key_details.token_family.slug);
+ GNUNET_free (key_details.token_family.name);
+ if (NULL != key_details.priv) {
+ GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key);
+ }
+
+ switch (key_details.token_family.kind) {
+ case TALER_MERCHANTDB_TFK_Subscription:
+ authority.kind = TALER_MCTK_SUBSCRIPTION;
+ authority.details.subscription.start_date = key_details.valid_after;
+ authority.details.subscription.end_date = key_details.valid_before;
+ authority.critical = true;
+ // TODO: Set trusted domains
+ break;
+ case TALER_MERCHANTDB_TFK_Discount:
+ authority.kind = TALER_MCTK_DISCOUNT;
+ authority.critical = false;
+ // TODO: Set expected domains
+ break;
+ }
+
+ GNUNET_array_append (oc->parse_choices.authorities,
+ oc->parse_choices.authorities_len,
+ authority);
+
+ return MHD_YES;
+}
/**
* Serialize order into @a oc->serialize_order.contract,
@@ -1276,45 +1470,159 @@ serialize_order (struct OrderContext *oc)
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
json_t *merchant;
+ json_t *token_types = json_object ();
+ json_t *choices = json_array ();
merchant = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("name",
- settings->name),
+ settings->name),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
- settings->website)),
+ settings->website)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("email",
- settings->email)),
+ settings->email)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("logo",
- settings->logo)));
+ settings->logo)));
GNUNET_assert (NULL != merchant);
{
- json_t *loca = settings->address;
+ json_t *loca;
+ /* Handle merchant address */
+ loca = settings->address;
if (NULL != loca)
{
+ loca = json_deep_copy (loca);
+ GNUNET_assert (NULL != loca);
GNUNET_assert (0 ==
- json_object_set (merchant,
- "address",
- loca));
+ json_object_set_new (merchant,
+ "address",
+ loca));
}
}
{
- json_t *juri = settings->jurisdiction;
+ json_t *juri;
/* Handle merchant jurisdiction */
+ juri = settings->jurisdiction;
if (NULL != juri)
{
+ juri = json_deep_copy (juri);
+ GNUNET_assert (NULL != juri);
GNUNET_assert (0 ==
- json_object_set (merchant,
- "jurisdiction",
- juri));
+ json_object_set_new (merchant,
+ "jurisdiction",
+ juri));
}
}
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i];
+
+ // TODO: Finish spec to clearly define how token families are stored in
+ // ContractTerms.
+ // Here are some thoughts:
+ // - Multiple keys of the same token family can be referrenced in
+ // one contract. E.g. exchanging old subscription for new.
+ // - TokenAuthority should be renamed to TokenFamily for consistency.
+ // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but
+ // every token-based in- or ouput needs to have a valid_after date,
+ // so it's clear with key is referenced.
+ json_t *jauthority = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("description",
+ authority->description),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ authority->description_i18n)),
+ GNUNET_JSON_pack_data_auto ("h_pub",
+ &authority->pub->public_key.pub_key_hash),
+ GNUNET_JSON_pack_data_auto ("pub",
+ &authority->pub->public_key.pub_key_hash),
+ GNUNET_JSON_pack_timestamp ("token_expiration",
+ authority->token_expiration)
+ );
+
+ GNUNET_assert (0 ==
+ json_object_set_new (token_types,
+ authority->label,
+ jauthority));
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i];
+
+ json_t *inputs = json_array ();
+ json_t *outputs = json_array ();
+
+ for (unsigned int j = 0; j<choice->inputs_len; j++)
+ {
+ struct TALER_MerchantContractInput *input = &choice->inputs[j];
+
+ json_t *jinput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ input->type)
+ );
+
+ if (TALER_MCIT_TOKEN == input->type)
+ {
+ GNUNET_assert(0 ==
+ json_object_set_new(jinput,
+ "number",
+ json_integer (
+ input->details.token.count)));
+ GNUNET_assert(0 ==
+ json_object_set_new(jinput,
+ "token_family_slug",
+ json_string (
+ input->details.token.token_family_slug)));
+ }
+
+ GNUNET_assert (0 == json_array_append_new (inputs, jinput));
+ }
+
+ for (unsigned int j = 0; j<choice->outputs_len; j++)
+ {
+ struct TALER_MerchantContractOutput *output = &choice->outputs[j];
+
+ json_t *joutput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ output->type)
+ );
+
+ if (TALER_MCOT_TOKEN == output->type)
+ {
+ GNUNET_assert(0 ==
+ json_object_set_new(joutput,
+ "number",
+ json_integer (
+ output->details.token.count)));
+
+ GNUNET_assert(0 ==
+ json_object_set_new(joutput,
+ "token_family_slug",
+ json_string (
+ output->details.token.token_family_slug)));
+ }
+
+ GNUNET_assert (0 == json_array_append (outputs, joutput));
+ }
+
+ json_t *jchoice = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_incref ("inputs",
+ inputs),
+ GNUNET_JSON_pack_array_incref ("outputs",
+ outputs)
+ );
+
+ GNUNET_assert (0 == json_array_append (choices, jchoice));
+ }
+
oc->serialize_order.contract = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("version",
+ oc->parse_order.version),
GNUNET_JSON_pack_string ("summary",
oc->parse_order.summary),
GNUNET_JSON_pack_allow_null (
@@ -1368,6 +1676,14 @@ serialize_order (struct OrderContext *oc)
TALER_JSON_pack_amount ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("choices",
+ choices)
+ ),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("token_types",
+ token_types)
+ ),
+ GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",
(json_t *) oc->parse_order.extra))
);
@@ -1378,6 +1694,7 @@ serialize_order (struct OrderContext *oc)
"refund_deadline",
GNUNET_JSON_from_timestamp (
oc->parse_order.refund_deadline)));
+
GNUNET_log (
GNUNET_ERROR_TYPE_INFO,
"Refund deadline for contact is %llu\n",
@@ -1401,7 +1718,6 @@ serialize_order (struct OrderContext *oc)
oc->phase++;
}
-
/**
* Set max_fee in @a oc based on STEFAN value if
* not yet present. Upon success, continue
@@ -1437,7 +1753,6 @@ set_max_fee (struct OrderContext *oc)
oc->phase++;
}
-
/**
* Set list of acceptable exchanges in @a oc. Upon success, continue
* processing with set_max_fee().
@@ -1503,8 +1818,8 @@ set_exchanges (struct OrderContext *oc)
/**
- * Add missing fields to the order. Upon success, continue
- * processing with merge_inventory().
+ * Parse the order field of the request. Upon success, continue
+ * processing with parse_choices().
*
* @param[in,out] oc order context
*/
@@ -1514,12 +1829,17 @@ parse_order (struct OrderContext *oc)
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
const char *merchant_base_url = NULL;
+ const char *version = NULL;
const json_t *jmerchant = NULL;
/* auto_refund only needs to be type-checked,
* mostly because in GNUnet relative times can't
* be negative. */
bool no_fee;
struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("version",
+ &version),
+ NULL),
TALER_JSON_spec_amount_any ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_spec_string ("summary",
@@ -1537,10 +1857,6 @@ parse_order (struct OrderContext *oc)
&oc->parse_order.order_id),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("public_reorder_url",
- &oc->parse_order.public_reorder_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("fulfillment_message",
&oc->parse_order.fulfillment_message),
NULL),
@@ -1553,6 +1869,14 @@ parse_order (struct OrderContext *oc)
&oc->parse_order.fulfillment_url),
NULL),
GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("public_reorder_url",
+ &oc->parse_order.public_reorder_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("choices",
+ &oc->parse_order.choices),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_web_url ("merchant_base_url",
&merchant_base_url),
NULL),
@@ -1616,6 +1940,46 @@ parse_order (struct OrderContext *oc)
ret);
return;
}
+ if (NULL == version || 0 == strcmp("0", version))
+ {
+ oc->parse_order.version = TALER_MCV_V0;
+
+ if (NULL != oc->parse_order.choices)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
+ "choices array must be null for v0 contracts");
+ return;
+ }
+ }
+ else if (0 == strcmp("1", version))
+ {
+ oc->parse_order.version = TALER_MCV_V1;
+
+ if (! json_is_array(oc->parse_order.choices))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order.choices is not a valid array");
+ return;
+ }
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_VERSION_MALFORMED,
+ "invalid version specified in order, supported are null, '0' or '1'");
+ return;
+ }
if (! TMH_test_exchange_configured_for_currency (
oc->parse_order.brutto.currency))
{
@@ -1628,9 +1992,9 @@ parse_order (struct OrderContext *oc)
return;
}
if ( (! no_fee) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&oc->parse_order.brutto,
- &oc->parse_order.max_fee)) )
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&oc->parse_order.brutto,
+ &oc->parse_order.max_fee)) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -1907,6 +2271,263 @@ parse_order (struct OrderContext *oc)
oc->phase++;
}
+/**
+ * Parse contract choices. Upon success, continue
+ * processing with merge_inventory().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+parse_choices (struct OrderContext *oc)
+{
+ if (NULL == oc->parse_order.choices)
+ {
+ oc->phase++;
+ return;
+ }
+
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ json_array_size (oc->parse_order.choices));
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ const char *error_name;
+ unsigned int error_line;
+ const json_t *jinputs;
+ const json_t *joutputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i),
+ spec,
+ &error_name,
+ &error_line);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Choice parsing failed: %s:%u\n",
+ error_name,
+ error_line);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "choice");
+ return;
+ }
+
+ if (! json_is_array (jinputs) ||
+ ! json_is_array (joutputs))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inputs or outputs");
+ return;
+ }
+
+ {
+ // TODO: Maybe move to a separate function
+ const json_t *jinput;
+ size_t idx;
+ json_array_foreach ((json_t *) jinputs, idx, jinput)
+ {
+ // TODO: Assuming this struct would have more fields, would i use GNUNET_new then?
+ // Or should i use GNUNET_grow first and then get the element using the index?
+ // Assuming you add a field in the future, it's easier to that way, so you don't
+ // free it.
+ struct TALER_MerchantContractInput input = {.details.token.count = 1};
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ 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_timestamp ("valid_after",
+ &input.details.token.valid_after),
+ 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;
+ }
+
+ input.type = TMH_string_to_contract_input_type (kind);
+
+ if (TALER_MCIT_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;
+ }
+
+ if (0 == input.details.token.count)
+ {
+ /* Ignore inputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ input.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ input.details.token.token_family_slug,
+ input.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ input);
+ }
+ }
+
+ {
+ const json_t *joutput;
+ size_t idx;
+ json_array_foreach ((json_t *) joutputs, idx, joutput)
+ {
+ struct TALER_MerchantContractOutput output = { .details.token.count = 1 };
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &output.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &output.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &output.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (joutput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ 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;
+ }
+
+ output.type = TMH_string_to_contract_output_type (kind);
+
+ if (TALER_MCOT_INVALID == output.type)
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ 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;
+ }
+
+ if (0 == output.details.token.count)
+ {
+ /* Ignore outputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ output.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ output.details.token.token_family_slug,
+ output.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ output);
+ }
+ }
+ }
+
+ oc->phase++;
+}
/**
* Process the @a payment_target and add the details of how the
@@ -2305,6 +2926,9 @@ TMH_private_post_orders (
case ORDER_PHASE_PARSE_ORDER:
parse_order (oc);
break;
+ case ORDER_PHASE_PARSE_CHOICES:
+ parse_choices (oc);
+ break;
case ORDER_PHASE_MERGE_INVENTORY:
merge_inventory (oc);
break;
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index e7fb15cf..defc3cf9 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -173,6 +173,8 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_lookup_token_families.h pg_lookup_token_families.c \
pg_delete_token_family.h pg_delete_token_family.c \
pg_update_token_family.h pg_update_token_family.c \
+ pg_insert_token_family_key.h pg_insert_token_family_key.c \
+ pg_lookup_token_family_key.h pg_lookup_token_family_key.c \
plugin_merchantdb_postgres.c \
pg_helper.h pg_helper.c
libtaler_plugin_merchantdb_postgres_la_LIBADD = \
diff --git a/src/backenddb/pg_insert_token_family.h b/src/backenddb/pg_insert_token_family.h
index e05755a6..d584f5e7 100644
--- a/src/backenddb/pg_insert_token_family.h
+++ b/src/backenddb/pg_insert_token_family.h
@@ -28,7 +28,7 @@
/**
* @param cls closure
- * @param instance_id instance to insert token family for TODO: Is this needed?
+ * @param instance_id instance to insert token family for
* @param token_family_slug slug of the token family to insert
* @param details the token family details to insert
* @return database result code
diff --git a/src/backenddb/pg_insert_token_family_key.c b/src/backenddb/pg_insert_token_family_key.c
new file mode 100644
index 00000000..30525d5f
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family_key.c
@@ -0,0 +1,95 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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
+ 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 backenddb/pg_insert_token_family_key.c
+ * @brief Implementation of the insert_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_pq_lib.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_token_family_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family_key (void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct GNUNET_TIME_Timestamp valid_after,
+ const struct GNUNET_TIME_Timestamp valid_before)
+{
+ struct PostgresClosure *pg = cls;
+ const char *cipher;
+ // struct GNUNET_HashCode pub_hash;
+
+ switch (pub->public_key.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = "rsa";
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = "cs";
+ break;
+ case GNUNET_CRYPTO_BSA_INVALID:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_blind_sign_pub (&pub->public_key),
+ GNUNET_PQ_query_param_auto_from_type (&pub->public_key.pub_key_hash),
+ GNUNET_PQ_query_param_blind_sign_priv (&priv->private_key),
+ GNUNET_PQ_query_param_timestamp (&valid_after),
+ GNUNET_PQ_query_param_timestamp (&valid_before),
+ GNUNET_PQ_query_param_string (cipher),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (pub->public_key.cipher == priv->private_key.cipher);
+ // TODO: Ensure pub->public_key.pub_key_hash matches the actual public key
+ // GNUNET_CRYPTO_hash (res,
+ // len,
+ // &bpk->pub_key_hash);
+ // GNUNET_assert (0 ==
+ // GNUNET_memcmp (&pub_hash,
+ // &pub->public_key.));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ valid_after.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ valid_before.abs_time));
+
+ PREPARE (pg,
+ "token_family_key_insert",
+ "INSERT INTO merchant_token_family_keys "
+ "(token_family_serial"
+ ",pub"
+ ",h_pub"
+ ",priv"
+ ",valid_after"
+ ",valid_before"
+ ",cipher)"
+ " SELECT token_family_serial, $2, $3, $4, $5, $6, $7"
+ " FROM merchant_token_families"
+ " WHERE slug = $1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "token_family_key_insert",
+ params);
+} \ No newline at end of file
diff --git a/src/backenddb/pg_insert_token_family_key.h b/src/backenddb/pg_insert_token_family_key.h
new file mode 100644
index 00000000..c4fc8d85
--- /dev/null
+++ b/src/backenddb/pg_insert_token_family_key.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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
+ 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 backenddb/pg_insert_token_family_key.h
+ * @brief implementation of the insert_token_family_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_TOKEN_FAMILY_KEY_H
+#define PG_INSERT_TOKEN_FAMILY_KEY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * @param cls closure
+ * @param token_family_slug slug of the token family to insert the key for
+ * @param pub public key to insert
+ * @param priv private key to insert
+ * @param valid_after start of validity period for this key
+ * @param valid_before end of validity period for this key
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_token_family_key (void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct GNUNET_TIME_Timestamp valid_after,
+ const struct GNUNET_TIME_Timestamp valid_before);
+
+#endif
diff --git a/src/backenddb/pg_lookup_token_family.c b/src/backenddb/pg_lookup_token_family.c
index 848b79a9..d2c651c9 100644
--- a/src/backenddb/pg_lookup_token_family.c
+++ b/src/backenddb/pg_lookup_token_family.c
@@ -105,9 +105,9 @@ TMH_PG_lookup_token_family (void *cls,
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
- if (strcmp(kind, "discount") == 0)
+ if (0 == strcmp(kind, "discount"))
details->kind = TALER_MERCHANTDB_TFK_Discount;
- else if (strcmp(kind, "subscription") == 0)
+ else if (0 == strcmp(kind, "subscription"))
details->kind = TALER_MERCHANTDB_TFK_Subscription;
else
{
diff --git a/src/backenddb/pg_lookup_token_family_key.c b/src/backenddb/pg_lookup_token_family_key.c
new file mode 100644
index 00000000..f1fa75f3
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family_key.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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
+ 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 backenddb/pg_lookup_token_family_key.c
+ * @brief Implementation of the lookup_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <gnunet/gnunet_pq_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <string.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_token_family_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family_key (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (token_family_slug),
+ GNUNET_PQ_query_param_timestamp (&min_valid_after),
+ GNUNET_PQ_query_param_timestamp (&max_valid_after),
+ GNUNET_PQ_query_param_end
+ };
+
+ if (NULL == details)
+ {
+ struct GNUNET_PQ_ResultSpec rs_null[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_token_family_key",
+ params,
+ rs_null);
+ }
+ else
+ {
+ char *kind;
+ details->pub = NULL;
+ details->priv = NULL;
+ details->valid_after = GNUNET_TIME_UNIT_ZERO_TS;
+ details->valid_before = GNUNET_TIME_UNIT_ZERO_TS;
+
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ // GNUNET_PQ_result_spec_allow_null (
+ // GNUNET_PQ_result_spec_blind_sign_pub ("pub",
+ // &details->pub->public_key),
+ // NULL),
+ // GNUNET_PQ_result_spec_allow_null (
+ // GNUNET_PQ_result_spec_blind_sign_priv ("priv",
+ // &details->priv->private_key),
+ // NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_after",
+ &details->valid_after),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_before",
+ &details->valid_before),
+ NULL),
+ GNUNET_PQ_result_spec_string ("slug",
+ &details->token_family.slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &details->token_family.name),
+ GNUNET_PQ_result_spec_string ("description",
+ &details->token_family.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &details->token_family.description_i18n),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &details->token_family.valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &details->token_family.valid_before),
+ GNUNET_PQ_result_spec_relative_time ("duration",
+ &details->token_family.duration),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_uint64 ("issued",
+ &details->token_family.issued),
+ GNUNET_PQ_result_spec_uint64 ("redeemed",
+ &details->token_family.redeemed),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_token_family_key",
+ "SELECT"
+ " h_pub"
+ ",pub"
+ ",priv"
+ ",cipher"
+ ",merchant_token_family_keys.valid_after as key_valid_after"
+ ",merchant_token_family_keys.valid_before as key_valid_before"
+ ",slug"
+ ",name"
+ ",description"
+ ",description_i18n"
+ ",merchant_token_families.valid_after"
+ ",merchant_token_families.valid_before"
+ ",duration"
+ ",kind"
+ ",issued"
+ ",redeemed"
+ " FROM merchant_token_families"
+ " LEFT JOIN merchant_token_family_keys"
+ " ON merchant_token_families.token_family_serial = merchant_token_family_keys.token_family_serial"
+ " AND merchant_token_family_keys.valid_after >= $3"
+ " AND merchant_token_family_keys.valid_after <= $4"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND slug=$2"
+ " LIMIT 1");
+ enum GNUNET_DB_QueryStatus qs;
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_token_family_key",
+ params,
+ rs);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 == strcmp(kind, "discount"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (0 == strcmp(kind, "subscription"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
+ else
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ /* TODO: How to handle multiple results? */
+
+ return qs;
+ }
+} \ No newline at end of file
diff --git a/src/backenddb/pg_lookup_token_family_key.h b/src/backenddb/pg_lookup_token_family_key.h
new file mode 100644
index 00000000..aa7335cf
--- /dev/null
+++ b/src/backenddb/pg_lookup_token_family_key.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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
+ 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 backenddb/pg_lookup_token_family_key.h
+ * @brief implementation of the lookup_token_family_key function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_LOOKUP_TOKEN_FAMILY_KEY_H
+#define PG_LOOKUP_TOKEN_FAMILY_KEY_H
+
+#include <gnunet/gnunet_common.h>
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup details about a particular token family key.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family key for
+ * @param token_family_slug slug of token family to lookup
+ * @param min_valid_after lower bound of the start of the key validation period
+ * @param max_valid_after upper bound of the start of the key validation period
+ * @param[out] details set to the token family key details on success, can be NULL
+ * (in that case we only want to check if the token family key exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_token_family_key (void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
+
+
+#endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index e09b4827..e5f3f2a1 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -138,6 +138,8 @@
#include "pg_lookup_token_families.h"
#include "pg_delete_token_family.h"
#include "pg_update_token_family.h"
+#include "pg_insert_token_family_key.h"
+#include "pg_lookup_token_family_key.h"
/**
@@ -575,9 +577,14 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_delete_token_family;
plugin->update_token_family
= &TMH_PG_update_token_family;
+ plugin->insert_token_family_key
+ = &TMH_PG_insert_token_family_key;
+ plugin->lookup_token_family_key
+ = &TMH_PG_lookup_token_family_key;
plugin->update_deposit_confirmation_status
= &TMH_PG_update_deposit_confirmation_status;
+
return plugin;
}
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index c83b3c93..44fdc0ab 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -23,6 +23,8 @@
#ifndef TALER_MERCHANTDB_PLUGIN_H
#define TALER_MERCHANTDB_PLUGIN_H
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_time_lib.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <taler/taler_exchange_service.h>
@@ -1094,17 +1096,17 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails
/**
* Token family public key.
*/
- struct TALER_TokenFamilyPublicKey pub;
+ struct TALER_TokenFamilyPublicKey *pub;
/**
- * Hash of the token family public key.
+ * Token family private key.
*/
- struct TALER_TokenFamilyPublicKeyHash pub_h;
+ struct TALER_TokenFamilyPrivateKey *priv;
/**
- * Token family private key.
- */
- struct TALER_TokenFamilyPrivateKey priv;
+ * Details about the token family this key belongs to.
+ */
+ struct TALER_MERCHANTDB_TokenFamilyDetails token_family;
};
/**
@@ -3240,7 +3242,7 @@ struct TALER_MERCHANTDB_Plugin
* Insert details about a particular token family.
*
* @param cls closure
- * @param instance_id instance to insert product for
+ * @param instance_id instance to insert token family for
* @param token_family_slug slug of token family to insert
* @param details the token family details to insert
* @return database result code
@@ -3252,6 +3254,49 @@ struct TALER_MERCHANTDB_Plugin
const char *token_family_slug,
const struct TALER_MERCHANTDB_TokenFamilyDetails *details);
+
+ /**
+ * Lookup details about a particular token family key.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup token family key for
+ * @param token_family_slug slug of token family to lookup
+ * @param min_valid_after lower bound of the start of the key validation period
+ * @param max_valid_after upper bound of the start of the key validation period
+ * @param[out] details set to the token family key details on success, can be NULL
+ * (in that case we only want to check if the token family key exists)
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_token_family_key) (
+ void *cls,
+ const char *instance_id,
+ const char *token_family_slug,
+ struct GNUNET_TIME_Timestamp min_valid_after,
+ struct GNUNET_TIME_Timestamp max_valid_after,
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails *details);
+
+
+ /**
+ * Insert details a key pair for a token family.
+ *
+ * @param cls closure
+ * @param token_family_slug slug of token family to insert the key pair for
+ * @param pub token family public key
+ * @param priv token family private key
+ * @param valid_after start of the key validation period
+ * @param valid_before end of the key validation period
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_token_family_key)(
+ void *cls,
+ const char *token_family_slug,
+ const struct TALER_TokenFamilyPublicKey *pub,
+ const struct TALER_TokenFamilyPrivateKey *priv,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct GNUNET_TIME_Timestamp valid_before);
+
/**
* Lookup deposits that are finished and awaiting a wire transfer.
*