commit 23f9dc877d7035292a8f72f20cbba695391d62b8
parent 1482298b70381c3b8da3ffa34637de962479a93e
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Mon, 26 Jan 2026 00:57:42 +0100
adding tips to the order contract #0010889
Diffstat:
6 files changed, 162 insertions(+), 61 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c
@@ -1367,22 +1367,26 @@ handle_phase_compute_price (struct UseContext *uc)
struct TALER_MERCHANT_ContractChoice choice
= tcp->choices[i];
- if ( (! uc->parse_request.no_tip) &&
- (GNUNET_YES !=
- TALER_amount_cmp_currency (&choice.amount,
- &uc->parse_request.tip)) )
- continue; /* tip does not match choice currency */
- if (0 >
- TALER_amount_add (&choice.amount,
- &choice.amount,
- &uc->parse_request.tip))
+ choice.no_tip = uc->parse_request.no_tip;
+ if (! uc->parse_request.no_tip)
{
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
- "tip");
- return;
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&choice.amount,
+ &uc->parse_request.tip))
+ continue; /* tip does not match choice currency */
+ choice.tip = uc->parse_request.tip;
+ if (0 >
+ TALER_amount_add (&choice.amount,
+ &choice.amount,
+ &uc->parse_request.tip))
+ {
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "tip");
+ return;
+ }
}
GNUNET_assert (0 ==
json_array_append_new (
@@ -1558,39 +1562,6 @@ handle_phase_check_total (struct UseContext *uc)
/**
- * Convert any "tip" specified by the customer into a
- * products array for the order.
- *
- * @param uc request context
- * @return NULL if there is no tip
- */
-// FIXME: change contract terms spec: a 'tip' is not a product, just put it into a new field 'tip'!
-static json_t *
-get_tip_product (const struct UseContext *uc)
-{
- json_t *tip_products;
-
- if (uc->parse_request.no_tip)
- return NULL;
- GNUNET_assert (1 == uc->compute_price.totals_len);
- tip_products = json_array ();
- GNUNET_assert (NULL != tip_products);
- GNUNET_assert (0 ==
- json_array_append_new (
- tip_products,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("description",
- "tip"),
- GNUNET_JSON_pack_uint64 ("quantity",
- 1),
- TALER_JSON_pack_amount (
- "price",
- &uc->parse_request.tip))));
- return tip_products;
-}
-
-
-/**
* Create order request for inventory templates.
*
* @param[in,out] uc use context
@@ -1598,7 +1569,6 @@ get_tip_product (const struct UseContext *uc)
static void
create_using_templates_inventory (struct UseContext *uc)
{
- json_t *tip_products = get_tip_product (uc);
json_t *inventory_products;
json_t *choices;
@@ -1627,7 +1597,12 @@ create_using_templates_inventory (struct UseContext *uc)
choices,
GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("amount",
- &uc->compute_price.totals[i])
+ &uc->compute_price.totals[i]),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("tip",
+ uc->parse_request.no_tip
+ ? NULL
+ : &uc->parse_request.tip))
)));
}
@@ -1648,10 +1623,7 @@ create_using_templates_inventory (struct UseContext *uc)
GNUNET_JSON_pack_string ("summary",
NULL == uc->parse_request.summary
? uc->template_contract.summary
- : uc->parse_request.summary),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("products",
- tip_products)))));
+ : uc->parse_request.summary))));
}
@@ -1663,8 +1635,6 @@ create_using_templates_inventory (struct UseContext *uc)
static void
create_using_templates_fixed (struct UseContext *uc)
{
- json_t *tip_products = get_tip_product (uc);
-
uc->ihc.request_body
= GNUNET_JSON_PACK (
GNUNET_JSON_pack_allow_null (
@@ -1676,14 +1646,16 @@ create_using_templates_fixed (struct UseContext *uc)
TALER_JSON_pack_amount (
"amount",
&uc->compute_price.totals[0]),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("tip",
+ uc->parse_request.no_tip
+ ? NULL
+ : &uc->parse_request.tip)),
GNUNET_JSON_pack_string (
"summary",
NULL == uc->parse_request.summary
? uc->template_contract.summary
- : uc->parse_request.summary),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("products",
- tip_products)))));
+ : uc->parse_request.summary))));
}
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -78,7 +78,7 @@
* refuses a forced download.
*/
#define MAX_KEYS_WAIT \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
/**
* Generate the base URL for the given merchant instance.
@@ -442,6 +442,16 @@ struct OrderContext
struct TALER_Amount brutto;
/**
+ * Tip included by the customer (part of the total amount).
+ */
+ struct TALER_Amount tip;
+
+ /**
+ * True if @e tip was not provided.
+ */
+ bool no_tip;
+
+ /**
* Maximum fee as given by the client request.
*/
struct TALER_Amount max_fee;
@@ -2397,6 +2407,11 @@ phase_serialize_order (struct OrderContext *oc)
xtra = GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("max_fee",
&oc->set_max_fee.details.v0.max_fee),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("tip",
+ oc->parse_order.details.v0.no_tip
+ ? NULL
+ : &oc->parse_order.details.v0.tip)),
TALER_JSON_pack_amount ("amount",
&oc->parse_order.details.v0.brutto));
break;
@@ -3771,6 +3786,10 @@ phase_parse_choices (struct OrderContext *oc)
TALER_JSON_spec_amount_any ("amount",
&choice->amount),
GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("tip",
+ &choice->tip),
+ &choice->no_tip),
+ GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_amount_any ("max_fee",
&choice->max_fee),
&no_fee),
@@ -3825,6 +3844,19 @@ phase_parse_choices (struct OrderContext *oc)
"different currencies used for 'max_fee' and 'amount' currency");
return;
}
+ if ( (! choice->no_tip) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&choice->amount,
+ &choice->tip)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ "tip and amount");
+ return;
+ }
if (! TMH_test_exchange_configured_for_currency (
choice->amount.currency))
@@ -4158,6 +4190,11 @@ phase_parse_order (struct OrderContext *oc)
&oc->parse_order.details.v0.brutto),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_amount_any (
+ "tip",
+ &oc->parse_order.details.v0.tip),
+ &oc->parse_order.details.v0.no_tip),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any (
"max_fee",
&oc->parse_order.details.v0.max_fee),
&no_fee),
@@ -4192,6 +4229,19 @@ phase_parse_order (struct OrderContext *oc)
"different currencies used for 'max_fee' and 'amount' currency");
return;
}
+ if ( (! oc->parse_order.details.v0.no_tip) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&oc->parse_order.details.v0.brutto,
+ &oc->parse_order.details.v0.tip)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ "tip and amount");
+ return;
+ }
if (! TMH_test_exchange_configured_for_currency (
oc->parse_order.details.v0.brutto.currency))
{
diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h
@@ -733,6 +733,16 @@ struct TALER_MERCHANT_ContractChoice
struct TALER_Amount amount;
/**
+ * Tip included by the customer (part of the total amount).
+ */
+ struct TALER_Amount tip;
+
+ /**
+ * True if @e tip was not provided.
+ */
+ bool no_tip;
+
+ /**
* Human readable description of the semantics of the choice within the
* contract to be shown to the user at payment.
*/
@@ -1235,6 +1245,16 @@ struct TALER_MERCHANT_Contract
struct TALER_Amount brutto;
/**
+ * Tip included by the customer (part of the total amount).
+ */
+ struct TALER_Amount tip;
+
+ /**
+ * True if @e tip was not provided.
+ */
+ bool no_tip;
+
+ /**
* Maximum fee as given by the client request.
*/
struct TALER_Amount max_fee;
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
@@ -2104,6 +2104,26 @@ run (void *cls,
NULL))),
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_merchant_post_using_templates2 (
+ "using-templates-inv-one-tip-ok",
+ "post-templates-inv-one",
+ NULL,
+ merchant_url,
+ "inv-1-tip-ok",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("amount",
+ "EUR:2"),
+ GNUNET_JSON_pack_string ("tip",
+ "EUR:1"),
+ GNUNET_JSON_pack_string ("template_type",
+ "inventory-cart"),
+ GNUNET_JSON_pack_array_steal (
+ "inventory_selection",
+ make_inventory_selection ("inv-product-1",
+ "1.0",
+ NULL,
+ NULL))),
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_using_templates2 (
"using-templates-inv-one-unknown",
"post-templates-inv-one",
NULL,
diff --git a/src/util/contract_parse.c b/src/util/contract_parse.c
@@ -381,6 +381,10 @@ parse_choices (
TALER_JSON_spec_amount_any ("amount",
&choice->amount),
GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("tip",
+ &choice->tip),
+ &choice->no_tip),
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string_copy ("description",
&choice->description),
NULL),
@@ -419,6 +423,17 @@ parse_choices (
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
+ if ( (! choice->no_tip) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&choice->amount,
+ &choice->tip)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Tip currency does not match amount currency in choice #%u\n",
+ i);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
if (NULL != jinputs)
{
@@ -1032,6 +1047,10 @@ parse_contract_v0 (
struct GNUNET_JSON_Specification espec[] = {
TALER_JSON_spec_amount_any ("amount",
&contract->details.v0.brutto),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("tip",
+ &contract->details.v0.tip),
+ &contract->details.v0.no_tip),
TALER_JSON_spec_amount_any ("max_fee",
&contract->details.v0.max_fee),
GNUNET_JSON_spec_end ()
@@ -1062,6 +1081,16 @@ parse_contract_v0 (
"'max_fee' in database does not match currency of contract price");
return GNUNET_SYSERR;
}
+ if ( (! contract->details.v0.no_tip) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&contract->details.v0.tip,
+ &contract->details.v0.brutto)) )
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "'tip' in database does not match currency of contract price");
+ return GNUNET_SYSERR;
+ }
return res;
}
diff --git a/src/util/contract_serialize.c b/src/util/contract_serialize.c
@@ -181,6 +181,11 @@ TALER_MERCHANT_json_from_contract_choice (
TALER_JSON_pack_amount ("amount",
&choice->amount),
GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("tip",
+ choice->no_tip
+ ? NULL
+ : &choice->tip)),
+ GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("description",
choice->description)),
GNUNET_JSON_pack_allow_null (
@@ -341,6 +346,11 @@ json_from_contract_v0 (
return GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("amount",
&input->details.v0.brutto),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("tip",
+ input->details.v0.no_tip
+ ? NULL
+ : &input->details.v0.tip)),
TALER_JSON_pack_amount ("max_fee",
&input->details.v0.max_fee));
}