summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-post-orders.c
diff options
context:
space:
mode:
authorChristian Blättler <blatc2@bfh.ch>2024-03-10 14:07:24 +0100
committerChristian Blättler <blatc2@bfh.ch>2024-03-10 14:07:24 +0100
commita290b4b02940bd702720b469a8c88a6ee931813b (patch)
tree7316d3fb8b549b46c4a39bf57f42c4703e0ac498 /src/backend/taler-merchant-httpd_private-post-orders.c
parent4b21df9887459e8a2c56f597ab5a2aefa3880ef1 (diff)
downloadmerchant-a290b4b02940bd702720b469a8c88a6ee931813b.tar.gz
merchant-a290b4b02940bd702720b469a8c88a6ee931813b.tar.bz2
merchant-a290b4b02940bd702720b469a8c88a6ee931813b.zip
implement parsing of choices array for v1 contracts
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c394
1 files changed, 241 insertions, 153 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index bea9fd51..1c25da4e 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -228,31 +228,14 @@ struct OrderContext
struct
{
/**
- * Our order ID.
- */
- const char *order_id;
-
- /**
- * 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;
-
- /**
- * Summary of the order.
+ * Version of the contract terms.
*/
- // const char *summary;
+ enum TALER_MerchantContractVersion version;
/**
- * Internationalized summary.
+ * Our order ID.
*/
- // json_t *summary_i18n;
+ const char *order_id;
/**
* URL where the same contract could be ordered again (if available).
@@ -260,21 +243,9 @@ struct OrderContext
const char *public_reorder_url;
/**
- * URL that will show that the order 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.
- */
- // const char *fulfillment_message;
-
- /**
- * Map from IETF BCP 47 language tags to localized fulfillment messages.
- */
- // json_t *fulfillment_message_i18n;
+ * Optional array of contract choices. Is null for v0 contracts.
+ */
+ const json_t *choices;
/**
* Merchant base URL.
@@ -312,38 +283,12 @@ struct OrderContext
json_t *delivery_location;
/**
- * Array of products that are part of the purchase.
- */
- // const json_t *products;
-
- /**
- * TODO: Maybe remove this and set it from settings where we serialize
- * the order to JSON?
- *
- * Information like name, website, email, etc. about the merchant.
- */
- // json_t *merchant;
-
- /**
- * TODO: Maybe remove this and set it from settings where we serialize
- * the order to JSON?
- *
- * Merchant's public key
- */
- // struct TALER_MerchantPublicKeyP merchant_pub;
-
- /**
* Gross amount value of the contract. Used to
* compute @e max_stefan_fee.
*/
struct TALER_Amount brutto;
/**
- * Maximum fee as given by the client request.
- */
- // struct TALER_Amount max_fee;
-
- /**
* Array of fee limits and wire account details by currency.
*/
struct TALER_MerchantContractLimits *limits;
@@ -373,6 +318,24 @@ 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;
+ } parse_choices;
+
+ /**
* Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
*/
struct
@@ -433,11 +396,6 @@ struct OrderContext
struct
{
/**
- * Maximum fee
- */
- // struct TALER_Amount max_fee;
-
- /**
* Array of fee limits and wire account details by currency.
*/
struct TALER_MerchantContractLimits *limits;
@@ -500,6 +458,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,
@@ -638,17 +597,17 @@ clean_order (void *cls)
oc->set_exchanges.exchanges = NULL;
}
/* TODO: Clean choices array */
- for (unsigned int i = 0; i<oc->parse_order.choices_len; i++)
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
{
- if (NULL != oc->parse_order.choices[i].fulfillment_message_i18n)
+ if (NULL != oc->parse_choices.choices[i].fulfillment_message_i18n)
{
- json_decref (oc->parse_order.choices[i].fulfillment_message_i18n);
- oc->parse_order.choices[i].fulfillment_message_i18n = NULL;
+ json_decref (oc->parse_choices.choices[i].fulfillment_message_i18n);
+ oc->parse_choices.choices[i].fulfillment_message_i18n = NULL;
}
- if (NULL != oc->parse_order.choices[i].summary_i18n)
+ if (NULL != oc->parse_choices.choices[i].summary_i18n)
{
- json_decref (oc->parse_order.choices[i].summary_i18n);
- oc->parse_order.choices[i].summary_i18n = NULL;
+ json_decref (oc->parse_choices.choices[i].summary_i18n);
+ oc->parse_choices.choices[i].summary_i18n = NULL;
}
if (NULL != oc->parse_order.delivery_location)
{
@@ -1351,7 +1310,7 @@ serialize_order (struct OrderContext *oc)
}
// TODO: How to properly handle these cases / errors?
- GNUNET_assert (1 == oc->parse_order.choices_len && NULL != choice);
+ GNUNET_assert (1 == oc->parse_choices.choices_len && NULL != choice);
GNUNET_assert (1 == oc->set_max_fee.limits_len && NULL != limits);
oc->serialize_order.contract = GNUNET_JSON_PACK (
@@ -1541,40 +1500,33 @@ 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
*/
static void
parse_order (struct OrderContext *oc)
{
- struct TALER_MerchantContractChoice *choice;
struct TALER_MerchantContractLimits *limits;
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;
- choice = GNUNET_new (struct TALER_MerchantContractChoice);
limits = GNUNET_new (struct TALER_MerchantContractLimits);
/* 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[] = {
- TALER_JSON_spec_amount_any ("amount",
- &oc->parse_order.brutto),
- GNUNET_JSON_spec_string ("summary",
- &choice->summary),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("products",
- &choice->products),
- NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("summary_i18n",
- &choice->summary_i18n),
+ GNUNET_JSON_spec_string ("version",
+ &version),
NULL),
+ TALER_JSON_spec_amount_any ("amount",
+ &oc->parse_order.brutto),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("order_id",
&oc->parse_order.order_id),
@@ -1584,16 +1536,8 @@ parse_order (struct OrderContext *oc)
&oc->parse_order.public_reorder_url),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_message",
- &choice->fulfillment_message),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("fulfillment_message_i18n",
- &choice->fulfillment_message_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_url",
- &choice->fulfillment_url),
+ GNUNET_JSON_spec_array_const ("choices",
+ &oc->parse_order.choices),
NULL),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_web_url ("merchant_base_url",
@@ -1655,6 +1599,34 @@ parse_order (struct OrderContext *oc)
ret);
return;
}
+ if (NULL == version || 0 == strcmp("v0", version))
+ {
+ oc->parse_order.version = TALER_MCV_V0;
+ }
+ else if (0 != strcmp("v1", version))
+ {
+ oc->parse_order.version = TALER_MCV_V1;
+ }
+ 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, 'v0' or 'v1'");
+ return;
+ }
+ if (NULL == oc->parse_order.choices && TALER_MCV_V0 != oc->parse_order.version)
+ {
+ 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 not be null for v1 contracts");
+ return;
+ }
if (! TMH_test_exchange_configured_for_currency (
oc->parse_order.brutto.currency))
{
@@ -1732,46 +1704,6 @@ parse_order (struct OrderContext *oc)
GNUNET_assert (NULL != oc->parse_order.order_id);
}
- /* Patch fulfillment URL with order_id (implements #6467). */
- if (NULL != choice->fulfillment_url)
- {
- const char *pos;
-
- pos = strstr (choice->fulfillment_url,
- "${ORDER_ID}");
- if (NULL != pos)
- {
- /* replace ${ORDER_ID} with the real order_id */
- char *nurl;
-
- /* We only allow one placeholder */
- if (strstr (pos + strlen ("${ORDER_ID}"),
- "${ORDER_ID}"))
- {
- GNUNET_break_op (0);
- reply_with_error (oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "fulfillment_url");
- return;
- }
-
- GNUNET_asprintf (&nurl,
- "%.*s%s%s",
- /* first output URL until ${ORDER_ID} */
- (int) (pos - choice->fulfillment_url),
- choice->fulfillment_url,
- /* replace ${ORDER_ID} with the right order_id */
- oc->parse_order.order_id,
- /* append rest of original URL */
- pos + strlen ("${ORDER_ID}"));
-
- choice->fulfillment_url = GNUNET_strdup (nurl);
-
- GNUNET_free (nurl);
- }
- }
-
/* Check soundness of refund deadline, and that a timestamp
* is actually present. */
{
@@ -1914,18 +1846,6 @@ parse_order (struct OrderContext *oc)
oc->parse_order.merchant_base_url = url;
}
- if ( (NULL != choice->products) &&
- (! TMH_products_array_valid (choice->products)) )
- {
- GNUNET_break_op (0);
- reply_with_error (
- oc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order.products");
- return;
- }
-
/* Merchant information must not already be present */
if (NULL != jmerchant)
{
@@ -1952,6 +1872,171 @@ parse_order (struct OrderContext *oc)
oc->phase++;
}
+/**
+ * Parse a json contract choice.
+ *
+ * @param[out] choice resulting contract choice
+ * @param root json object
+ * @param order_id order_id to be replaced in fulfillment URL
+ * @return #GNUNET_OK if choice could be parsed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_choice (
+ struct TALER_MerchantContractChoice *choice,
+ json_t *root,
+ const char *order_id)
+{
+ const char *error_name;
+ unsigned int error_line;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("summary",
+ &choice->summary),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("products",
+ &choice->products),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("summary_i18n",
+ &choice->summary_i18n),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("fulfillment_message",
+ &choice->fulfillment_message),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("fulfillment_message_i18n",
+ &choice->fulfillment_message_i18n),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &choice->fulfillment_url),
+ NULL),
+ };
+
+ enum GNUNET_GenericReturnValue ret;
+ ret = GNUNET_JSON_parse (root,
+ 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);
+ return ret;
+ }
+
+ /* Patch fulfillment URL with order_id (implements #6467). */
+ if (NULL != choice->fulfillment_url)
+ {
+ const char *pos;
+
+ pos = strstr (choice->fulfillment_url,
+ "${ORDER_ID}");
+ if (NULL != pos)
+ {
+ /* replace ${ORDER_ID} with the real order_id */
+ char *nurl;
+
+ /* We only allow one placeholder */
+ if (strstr (pos + strlen ("${ORDER_ID}"),
+ "${ORDER_ID}"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Patching fulfillment URL failed\n");
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_asprintf (&nurl,
+ "%.*s%s%s",
+ /* first output URL until ${ORDER_ID} */
+ (int) (pos - choice->fulfillment_url),
+ choice->fulfillment_url,
+ /* replace ${ORDER_ID} with the right order_id */
+ order_id,
+ /* append rest of original URL */
+ pos + strlen ("${ORDER_ID}"));
+
+ choice->fulfillment_url = GNUNET_strdup (nurl);
+
+ GNUNET_free (nurl);
+ }
+ }
+
+ if ( (NULL != choice->products) &&
+ (! TMH_products_array_valid (choice->products)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid choice.products array\n");
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+/**
+ * Check contract version and call parse_choices_v0 or parse_choices_v1
+ * respectively. Upon success, continue processing with merge_inventory().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+parse_choices (struct OrderContext *oc)
+{
+ enum GNUNET_GenericReturnValue ret;
+
+ if (TALER_MCV_V0 == oc->parse_order.version)
+ {
+ /**
+ * v0 contract is basically a v1 contract with only one choice
+ * and no inputs or outputs.
+ */
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ 1);
+
+ ret = parse_choice(oc->parse_choices.choices,
+ oc->parse_request.order,
+ oc->parse_order.order_id);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order");
+ return;
+ }
+ }
+ else if (TALER_MCV_V1 == oc->parse_order.version)
+ {
+ GNUNET_assert (NULL != oc->parse_order.choices);
+
+ 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++)
+ {
+ ret = parse_choice(&oc->parse_choices.choices[i],
+ json_array_get (oc->parse_order.choices, i),
+ oc->parse_order.order_id);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "choices");
+ return;
+ }
+ }
+ }
+
+ oc->phase++;
+}
/**
* Process the @a payment_target and add the details of how the
@@ -1999,9 +2084,9 @@ add_payment_details (struct OrderContext *oc)
static void
merge_inventory (struct OrderContext *oc)
{
- struct TALER_MerchantContractChoice *choice = oc->parse_order.choices;
+ struct TALER_MerchantContractChoice *choice = oc->parse_choices.choices;
- GNUNET_assert (1 == oc->parse_order.choices_len && NULL != choice);
+ GNUNET_assert (1 == oc->parse_choices.choices_len && NULL != choice);
/**
* parse_request.inventory_products => instructions to add products to contract terms
@@ -2338,6 +2423,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;