merchant

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

commit cf9ae8dba232ab06638a3a1800569d28a54167d1
parent 7e6fa590cc4d5ca53615bbe7889a57d2fcbb5a96
Author: Florian Dold <dold@taler.net>
Date:   Fri, 21 Aug 2026 12:37:59 +0200

merchant backend: improve customer-facing order responses

Diffstat:
Msrc/backend/taler-merchant-httpd_get-orders-ORDER_ID.c | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/backend/taler-merchant-httpd_qr.c | 7++++---
2 files changed, 172 insertions(+), 10 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-orders-ORDER_ID.c b/src/backend/taler-merchant-httpd_get-orders-ORDER_ID.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2024 Taler Systems SA + (C) 2014-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -739,6 +739,9 @@ phase_lookup_terms (struct GetOrderData *god) struct TALER_MerchantPostDataHashP unused; enum GNUNET_DB_QueryStatus qs; + /* No contract exists yet, so no payment choice has been selected. */ + god->choice_index = -1; + qs = TALER_MERCHANTDB_get_order ( TMH_db, god->hc->instance->settings.id, @@ -1031,6 +1034,89 @@ get_order_summary (const struct GetOrderData *god) /** + * Return the localized fulfillment message of the contract of @a god. + * + * @param god order to extract the fulfillment message from + * @return localized message, or NULL if no message was provided + */ +static const char * +get_fulfillment_message (const struct GetOrderData *god) +{ + const char *language_pattern; + const char *ret; + json_t *terms; + + if (NULL != god->contract_terms_json) + terms = god->contract_terms_json; + else + terms = god->order_json; + language_pattern = MHD_lookup_connection_value ( + god->sc.con, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT_LANGUAGE); + if (NULL == language_pattern) + language_pattern = "en"; + ret = json_string_value (TALER_JSON_extract_i18n ( + terms, + language_pattern, + "fulfillment_message")); + if (NULL == ret) + ret = god->ct->fulfillment_message; + return ret; +} + + +/** + * Return the amount to display for the order. Before a version 1 order with + * multiple choices is claimed, the exact amount is selected by the wallet. + * + * @param god order to extract the amount from + * @param[out] multiple_payment_options set if the wallet selects the amount + * @return order amount, or NULL if no single amount is available + */ +static const struct TALER_Amount * +get_order_amount (const struct GetOrderData *god, + bool *multiple_payment_options) +{ + *multiple_payment_options = false; + switch (god->ct->version) + { + case TALER_MERCHANT_CONTRACT_VERSION_0: + return (NULL != god->pc) + ? &god->pc->details.v0.brutto + : &god->order->details.v0.brutto; + case TALER_MERCHANT_CONTRACT_VERSION_1: + { + unsigned int choices_len = (NULL != god->pc) + ? god->pc->details.v1.choices_len + : god->order->details.v1.choices_len; + + if (god->choice_index >= 0) + { + if (god->choice_index >= choices_len) + { + GNUNET_break (0); + return NULL; + } + return (NULL != god->pc) + ? &god->pc->details.v1.choices[god->choice_index].amount + : &god->order->details.v1.choices[god->choice_index].amount; + } + if (1 == choices_len) + return (NULL != god->pc) + ? &god->pc->details.v1.choices[0].amount + : &god->order->details.v1.choices[0].amount; + *multiple_payment_options = true; + return NULL; + } + default: + GNUNET_break (0); + return NULL; + } +} + + +/** * The client did not yet pay, send it the payment request. * * @param god check pay request context @@ -1127,6 +1213,15 @@ send_pay_request (struct GetOrderData *god, { char *qr; + bool multiple_payment_options; + const struct TALER_Amount *order_amount; + struct GNUNET_TIME_Timestamp pay_deadline; + + order_amount = get_order_amount (god, + &multiple_payment_options); + pay_deadline = (NULL != god->pc) + ? god->pc->pay_deadline + : god->order->pay_deadline; qr = TMH_create_qrcode (taler_pay_uri); if (NULL == qr) @@ -1148,7 +1243,20 @@ send_pay_request (struct GetOrderData *god, GNUNET_JSON_pack_string ("taler_pay_qrcode_svg", qr), GNUNET_JSON_pack_string ("order_summary", - get_order_summary (god))); + get_order_summary (god)), + GNUNET_JSON_pack_string ("order_id", + god->order_id), + GNUNET_JSON_pack_string ("merchant_name", + god->hc->instance->settings.name), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("order_amount", + order_amount)), + GNUNET_JSON_pack_bool ("multiple_payment_options", + multiple_payment_options), + GNUNET_JSON_pack_bool ("claimed", + god->claimed), + GNUNET_JSON_pack_timestamp ("pay_deadline", + pay_deadline)); res = TALER_TEMPLATING_reply ( god->sc.con, MHD_HTTP_PAYMENT_REQUIRED, @@ -1598,20 +1706,36 @@ phase_return_status (struct GetOrderData *god) if (god->refund_pending) { + struct TALER_Amount refund_remaining; char *qr; char *uri; + char *order_status_url; GNUNET_assert (NULL != god->contract_terms_json); GNUNET_assert (NULL != god->contract_terms); + GNUNET_assert (0 <= + TALER_amount_subtract (&refund_remaining, + &god->refund_amount, + &god->refund_taken)); uri = make_taler_refund_uri (god->pc->merchant_base_url, god->order_id); - if (NULL == uri) + order_status_url = TMH_make_order_status_url ( + god->sc.con, + god->order_id, + god->session_id, + god->hc->instance->settings.id, + NULL, + &god->h_contract_terms); + if ( (NULL == uri) || + (NULL == order_status_url) ) { GNUNET_break (0); + GNUNET_free (uri); + GNUNET_free (order_status_url); phase_fail (god, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_ALLOCATION_FAILURE, - "refund URI"); + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, + "host"); return; } qr = TMH_create_qrcode (uri); @@ -1619,6 +1743,7 @@ phase_return_status (struct GetOrderData *god) { GNUNET_break (0); GNUNET_free (uri); + GNUNET_free (order_status_url); phase_fail (god, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_ALLOCATION_FAILURE, @@ -1637,6 +1762,17 @@ phase_return_status (struct GetOrderData *god) &god->refund_amount), TALER_JSON_pack_amount ("refund_taken", &god->refund_taken), + TALER_JSON_pack_amount ("refund_remaining", + &refund_remaining), + GNUNET_JSON_pack_bool ("has_refund_taken", + ! TALER_amount_is_zero ( + &god->refund_taken)), + GNUNET_JSON_pack_string ("order_id", + god->order_id), + GNUNET_JSON_pack_string ("merchant_name", + god->hc->instance->settings.name), + GNUNET_JSON_pack_string ("order_status_url", + order_status_url), GNUNET_JSON_pack_string ("taler_refund_uri", uri), GNUNET_JSON_pack_string ("taler_refund_qrcode_svg", @@ -1656,13 +1792,22 @@ phase_return_status (struct GetOrderData *god) : MHD_YES); } GNUNET_free (uri); + GNUNET_free (order_status_url); GNUNET_free (qr); return; } { enum GNUNET_GenericReturnValue res; + bool multiple_payment_options; + const struct TALER_Amount *order_amount; json_t *context; + const char *fulfillment_message; + + order_amount = get_order_amount (god, + &multiple_payment_options); + GNUNET_break (! multiple_payment_options); + fulfillment_message = get_fulfillment_message (god); context = GNUNET_JSON_PACK ( GNUNET_JSON_pack_object_incref ("contract_terms", @@ -1674,7 +1819,22 @@ phase_return_status (struct GetOrderData *god) TALER_JSON_pack_amount ("refund_amount", &god->refund_amount), TALER_JSON_pack_amount ("refund_taken", - &god->refund_taken)); + &god->refund_taken), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_amount ("order_amount", + order_amount)), + GNUNET_JSON_pack_string ("order_id", + god->order_id), + GNUNET_JSON_pack_string ("merchant_name", + god->hc->instance->settings.name), + GNUNET_JSON_pack_bool ("has_refund", + god->refunded), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_message", + fulfillment_message)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_url", + god->ct->fulfillment_url))); res = TALER_TEMPLATING_reply ( god->sc.con, MHD_HTTP_OK, @@ -1703,6 +1863,7 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh, if (NULL == god) { god = GNUNET_new (struct GetOrderData); + god->choice_index = -1; hc->ctx = god; hc->cc = &god_cleanup; god->sc.con = connection; diff --git a/src/backend/taler-merchant-httpd_qr.c b/src/backend/taler-merchant-httpd_qr.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-2026 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 @@ -37,7 +37,7 @@ TMH_create_qrcode (const char *uri) struct GNUNET_Buffer buf = { 0 }; qri = QRinput_new2 (0, - QR_ECLEVEL_M); + QR_ECLEVEL_H); if (NULL == qri) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, @@ -73,8 +73,9 @@ TMH_create_qrcode (const char *uri) } QRinput_free (qri); GNUNET_buffer_write_fstr (&buf, - "<svg width='100mm' height='100mm' viewBox='0 0 %u %u' " + "<svg width='100%%' height='100%%' viewBox='0 0 %u %u' " "version='1.1' xmlns='http://www.w3.org/2000/svg' " + "aria-hidden='true' focusable='false' " "style='shape-rendering: crispedges;'>\n", qrc->width, qrc->width);