summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-post-orders.c
diff options
context:
space:
mode:
authorChristian Blättler <blatc2@bfh.ch>2024-04-10 08:01:28 +0200
committerChristian Blättler <blatc2@bfh.ch>2024-04-10 08:01:28 +0200
commitd42cb68e0ce55310aac0ed0fb83e57ede8e63836 (patch)
treec26cd5c4704eed3e87a45b52a5f5fac7026d8049 /src/backend/taler-merchant-httpd_private-post-orders.c
parent983ad0943b4f0b9ddf7d1b7a14693e4b69395ea6 (diff)
parentd4fd038d116381d76d1fdf8384101f8fa901ffe5 (diff)
downloadmerchant-d42cb68e0ce55310aac0ed0fb83e57ede8e63836.tar.gz
merchant-d42cb68e0ce55310aac0ed0fb83e57ede8e63836.tar.bz2
merchant-d42cb68e0ce55310aac0ed0fb83e57ede8e63836.zip
Merge branch 'master' into tokens
# Conflicts: # src/backend/taler-merchant-httpd_private-post-orders.c
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c440
1 files changed, 245 insertions, 195 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index 8a093813..b2070bcc 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -49,6 +49,11 @@
#define MAX_RETRIES 3
/**
+ * Maximum number of inventory products per order.
+ */
+#define MAX_PRODUCTS 1024
+
+/**
* What is the label under which we find/place the merchant's
* jurisdiction in the locations list by default?
*/
@@ -346,6 +351,11 @@ struct OrderContext
*/
const json_t *extra;
+ /**
+ * Minimum age required by the order.
+ */
+ uint32_t minimum_age;
+
} parse_order;
/**
@@ -451,6 +461,24 @@ struct OrderContext
* Which product (by offset) is out of stock, UINT_MAX if all were in-stock.
*/
unsigned int out_of_stock_index;
+
+ /**
+ * Set to a previous claim token *if* @e idempotent
+ * is also true.
+ */
+ struct TALER_ClaimTokenP token;
+
+ /**
+ * Set to true if the order was idempotent and there
+ * was an equivalent one before.
+ */
+ bool idempotent;
+
+ /**
+ * Set to true if the order is in conflict with a
+ * previous order with the same order ID.
+ */
+ bool conflict;
} execute_order;
struct
@@ -668,7 +696,6 @@ clean_order (void *cls)
oc->parse_request.uuids_length,
0);
json_decref (oc->parse_request.order);
- /* TODO: Check that all other fields are cleaned up! */
json_decref (oc->serialize_order.contract);
GNUNET_free (oc->parse_order.merchant_base_url);
GNUNET_free (oc);
@@ -695,6 +722,45 @@ execute_transaction (struct OrderContext *oc)
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+
+ /* Test if we already have an order with this id */
+ {
+ json_t *contract_terms;
+ struct TALER_MerchantPostDataHashP orig_post;
+
+ qs = TMH_db->lookup_order (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
+ &oc->execute_order.token,
+ &orig_post,
+ &contract_terms);
+ /* If yes, check for idempotency */
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ json_decref (contract_terms);
+ /* Comparing the contract terms is sufficient because all the other
+ params get added to it at some point. */
+ if (0 == GNUNET_memcmp (&orig_post,
+ &oc->parse_request.h_post_data))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation idempotent\n");
+ oc->execute_order.idempotent = true;
+ return qs;
+ }
+ GNUNET_break_op (0);
+ oc->execute_order.conflict = true;
+ return qs;
+ }
+ }
+
/* Setup order */
qs = TMH_db->insert_order (TMH_db->cls,
oc->hc->instance->settings.id,
@@ -791,70 +857,6 @@ execute_order (struct OrderContext *oc)
&oc->hc->instance->settings;
enum GNUNET_DB_QueryStatus qs;
- /* Test if we already have an order with this id */
- /* FIXME: this should be done within the main
- transaction! */
- {
- struct TALER_ClaimTokenP token;
- json_t *contract_terms;
- struct TALER_MerchantPostDataHashP orig_post;
-
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->lookup_order (TMH_db->cls,
- oc->hc->instance->settings.id,
- oc->parse_order.order_id,
- &token,
- &orig_post,
- &contract_terms);
- /* If yes, check for idempotency */
- if (0 > qs)
- {
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- reply_with_error (oc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_order");
- return;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- MHD_RESULT ret;
-
- json_decref (contract_terms);
- /* Comparing the contract terms is sufficient because all the other
- params get added to it at some point. */
- if (0 == GNUNET_memcmp (&orig_post,
- &oc->parse_request.h_post_data))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Order creation idempotent\n");
- ret = TALER_MHD_REPLY_JSON_PACK (
- oc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("order_id",
- oc->parse_order.order_id),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_varsize (
- "token",
- GNUNET_is_zero (&token)
- ? NULL
- : &token,
- sizeof (token))));
- finalize_order (oc,
- ret);
- return;
- }
- /* This request is not idempotent */
- GNUNET_break_op (0);
- reply_with_error (
- oc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
- oc->parse_order.order_id);
- return;
- }
- }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Executing database transaction to create order '%s' for instance '%s'\n",
oc->parse_order.order_id,
@@ -899,6 +901,37 @@ execute_order (struct OrderContext *oc)
return;
}
+ /* DB transaction succeeded, check for idempotent */
+ if (oc->execute_order.idempotent)
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ oc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("order_id",
+ oc->parse_order.order_id),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_varsize (
+ "token",
+ GNUNET_is_zero (&oc->execute_order.token)
+ ? NULL
+ : &oc->execute_order.token,
+ sizeof (oc->execute_order.token))));
+ finalize_order (oc,
+ ret);
+ return;
+ }
+ if (oc->execute_order.conflict)
+ {
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
+ oc->parse_order.order_id);
+ return;
+ }
+
/* DB transaction succeeded, check for out-of-stock */
if (oc->execute_order.out_of_stock_index < UINT_MAX)
{
@@ -1436,161 +1469,155 @@ serialize_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
- json_t *merchant = NULL;
+ 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),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("website",
+ settings->website)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("email",
+ settings->email)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("logo",
+ settings->logo)));
+ GNUNET_assert (NULL != merchant);
{
- merchant = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("name",
- settings->name),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("website",
- settings->website)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("email",
- settings->email)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("logo",
- settings->logo)));
- GNUNET_assert (NULL != merchant);
- {
- json_t *loca;
+ 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_new (merchant,
- "address",
- loca));
- }
- }
+ /* Handle merchant address */
+ loca = settings->address;
+ if (NULL != loca)
{
- 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_new (merchant,
- "jurisdiction",
- juri));
- }
+ loca = json_deep_copy (loca);
+ GNUNET_assert (NULL != loca);
+ GNUNET_assert (0 ==
+ json_object_set_new (merchant,
+ "address",
+ loca));
}
}
-
{
- 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)
- );
+ 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_new (token_types,
- authority->label,
- jauthority));
+ json_object_set_new (merchant,
+ "jurisdiction",
+ juri));
}
}
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
{
- for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
- {
- struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i];
+ struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i];
- json_t *inputs = json_array ();
- json_t *outputs = json_array ();
+ // 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)
+ );
- for (unsigned int j = 0; j<choice->inputs_len; j++)
- {
- struct TALER_MerchantContractInput *input = &choice->inputs[j];
+ GNUNET_assert (0 ==
+ json_object_set_new (token_types,
+ authority->label,
+ jauthority));
+ }
- json_t *jinput = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("type",
- input->type)
- );
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i];
- 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)));
- }
+ json_t *inputs = json_array ();
+ json_t *outputs = json_array ();
- GNUNET_assert (0 == json_array_append_new (inputs, jinput));
- }
+ for (unsigned int j = 0; j<choice->inputs_len; j++)
+ {
+ struct TALER_MerchantContractInput *input = &choice->inputs[j];
- for (unsigned int j = 0; j<choice->outputs_len; j++)
- {
- struct TALER_MerchantContractOutput *output = &choice->outputs[j];
+ json_t *jinput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ input->type)
+ );
- json_t *joutput = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("type",
- output->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)));
+ }
- 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_new (inputs, jinput));
+ }
- GNUNET_assert (0 == json_array_append (outputs, joutput));
- }
+ for (unsigned int j = 0; j<choice->outputs_len; j++)
+ {
+ struct TALER_MerchantContractOutput *output = &choice->outputs[j];
- json_t *jchoice = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_incref ("inputs",
- inputs),
- GNUNET_JSON_pack_array_incref ("outputs",
- outputs)
+ json_t *joutput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ output->type)
);
- GNUNET_assert (0 == json_array_append (choices, jchoice));
+ 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 (
@@ -1613,6 +1640,9 @@ serialize_order (struct OrderContext *oc)
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_url",
oc->parse_order.fulfillment_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_uint64 ("minimum_age",
+ oc->parse_order.minimum_age)),
GNUNET_JSON_pack_array_incref ("products",
oc->merge_inventory.products),
GNUNET_JSON_pack_data_auto ("h_wire",
@@ -1635,8 +1665,8 @@ serialize_order (struct OrderContext *oc)
oc->parse_order.delivery_location)),
GNUNET_JSON_pack_string ("merchant_base_url",
oc->parse_order.merchant_base_url),
- GNUNET_JSON_pack_object_incref ("merchant",
- merchant),
+ GNUNET_JSON_pack_object_steal ("merchant",
+ merchant),
GNUNET_JSON_pack_data_auto ("merchant_pub",
&oc->hc->instance->merchant_pub),
GNUNET_JSON_pack_array_incref ("exchanges",
@@ -1883,6 +1913,10 @@ parse_order (struct OrderContext *oc)
&oc->parse_order.delivery_date),
NULL),
GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &oc->parse_order.minimum_age),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("auto_refund",
&oc->parse_order.auto_refund),
NULL),
@@ -2600,6 +2634,9 @@ merge_inventory (struct OrderContext *oc)
ip->product_id);
return;
}
+ oc->parse_order.minimum_age
+ = GNUNET_MAX (oc->parse_order.minimum_age,
+ pd.minimum_age);
{
json_t *p;
@@ -2778,9 +2815,22 @@ parse_request (struct OrderContext *oc)
/* parse the inventory_products (optionally given) */
if (NULL != ip)
{
+ unsigned int ipl = (unsigned int) json_array_size (ip);
+
+ if ( (json_array_size (ip) != (size_t) ipl) ||
+ (ipl > MAX_PRODUCTS) )
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "inventory products too long");
+ return;
+ }
GNUNET_array_grow (oc->parse_request.inventory_products,
oc->parse_request.inventory_products_length,
- json_array_size (ip));
+ (unsigned int) json_array_size (ip));
for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i];