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:
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);