merchant

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

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:
Msrc/backend/taler-merchant-httpd_post-using-templates.c | 92++++++++++++++++++++++++++++---------------------------------------------------
Msrc/backend/taler-merchant-httpd_private-post-orders.c | 52+++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/include/taler_merchant_util.h | 20++++++++++++++++++++
Msrc/testing/test_merchant_api.c | 20++++++++++++++++++++
Msrc/util/contract_parse.c | 29+++++++++++++++++++++++++++++
Msrc/util/contract_serialize.c | 10++++++++++
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)); }