commit 0edb2ad9afbe76d2c738138278a94630878d20a0
parent 7b365a122fdffec83c2cf7bd969bd3263c387e59
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 29 Mar 2026 20:52:38 +0200
fix a few minor API inconsistencies, especially in the C APIs
Diffstat:
9 files changed, 328 insertions(+), 30 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_get-private-reports-REPORT_ID.c b/src/backend/taler-merchant-httpd_get-private-reports-REPORT_ID.c
@@ -114,7 +114,10 @@ TMH_private_get_report (const struct TMH_RequestHandler *rh,
GNUNET_JSON_pack_time_rel ("report_frequency",
frequency),
GNUNET_JSON_pack_time_rel ("report_frequency_shift",
- frequency_shift));
+ frequency_shift),
+ GNUNET_JSON_pack_timestamp (
+ "next_transmission",
+ GNUNET_TIME_absolute_to_timestamp (next_transmission)));
GNUNET_free (report_program_section);
GNUNET_free (report_description);
GNUNET_free (mime_type);
diff --git a/src/include/taler/taler-merchant/get-orders-ORDER_ID.h b/src/include/taler/taler-merchant/get-orders-ORDER_ID.h
@@ -53,7 +53,22 @@ enum TALER_MERCHANT_GetOrdersOption
/**
* If true, wait until refund is confirmed obtained.
*/
- TALER_MERCHANT_GET_ORDERS_OPTION_AWAIT_REFUND_OBTAINED
+ TALER_MERCHANT_GET_ORDERS_OPTION_AWAIT_REFUND_OBTAINED,
+
+ /**
+ * Hash of the contract terms (for authentication).
+ */
+ TALER_MERCHANT_GET_ORDERS_OPTION_H_CONTRACT,
+
+ /**
+ * Claim token for unclaimed order authentication.
+ */
+ TALER_MERCHANT_GET_ORDERS_OPTION_TOKEN,
+
+ /**
+ * Whether refunded orders count for repurchase detection.
+ */
+ TALER_MERCHANT_GET_ORDERS_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE
};
@@ -99,6 +114,24 @@ struct TALER_MERCHANT_GetOrdersOptionValue
*/
bool await_refund_obtained;
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_GET_ORDERS_OPTION_H_CONTRACT.
+ */
+ struct TALER_PrivateContractHashP h_contract;
+
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_GET_ORDERS_OPTION_TOKEN.
+ */
+ struct TALER_ClaimTokenP token;
+
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_GET_ORDERS_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE.
+ */
+ enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
+
} details;
};
@@ -118,15 +151,13 @@ struct TALER_MERCHANT_GetOrdersHandle;
* @param ctx the context
* @param url base URL of the merchant backend
* @param order_id identifier of the order to check
- * @param h_contract hash of the contract terms (for authentication)
* @return handle to operation
*/
struct TALER_MERCHANT_GetOrdersHandle *
TALER_MERCHANT_get_orders_create (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const char *order_id,
- const struct TALER_PrivateContractHashP *h_contract);
+ const char *order_id);
/**
@@ -192,6 +223,45 @@ TALER_MERCHANT_get_orders_create (
.details.await_refund_obtained = (b) \
}
+/**
+ * Set hash of the contract terms (for authentication).
+ *
+ * @param h hash of contract terms
+ * @return representation of the option as a struct TALER_MERCHANT_GetOrdersOptionValue
+ */
+#define TALER_MERCHANT_get_orders_option_h_contract(h) \
+ (const struct TALER_MERCHANT_GetOrdersOptionValue) \
+ { \
+ .option = TALER_MERCHANT_GET_ORDERS_OPTION_H_CONTRACT, \
+ .details.h_contract = (h) \
+ }
+
+/**
+ * Set claim token for unclaimed order authentication.
+ *
+ * @param t claim token
+ * @return representation of the option as a struct TALER_MERCHANT_GetOrdersOptionValue
+ */
+#define TALER_MERCHANT_get_orders_option_token(t) \
+ (const struct TALER_MERCHANT_GetOrdersOptionValue) \
+ { \
+ .option = TALER_MERCHANT_GET_ORDERS_OPTION_TOKEN, \
+ .details.token = (t) \
+ }
+
+/**
+ * Set whether refunded orders count for repurchase detection.
+ *
+ * @param v yes/no/all value
+ * @return representation of the option as a struct TALER_MERCHANT_GetOrdersOptionValue
+ */
+#define TALER_MERCHANT_get_orders_option_allow_refunded_for_repurchase(v) \
+ (const struct TALER_MERCHANT_GetOrdersOptionValue) \
+ { \
+ .option = TALER_MERCHANT_GET_ORDERS_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE, \
+ .details.allow_refunded_for_repurchase = (v) \
+ }
+
/**
* Set the requested options for the operation.
diff --git a/src/include/taler/taler-merchant/post-orders-ORDER_ID-abort.h b/src/include/taler/taler-merchant/post-orders-ORDER_ID-abort.h
@@ -129,6 +129,46 @@ struct TALER_MERCHANT_PostOrdersAbortResponse
} ok;
+ /**
+ * Details on #MHD_HTTP_BAD_GATEWAY.
+ * The response body has the same refunds array as on
+ * #MHD_HTTP_OK, but at least one coin had an exchange error.
+ */
+ struct
+ {
+
+ /**
+ * Number of aborted coins in @a aborts.
+ */
+ unsigned int num_aborts;
+
+ /**
+ * Array of aborted coin results.
+ */
+ const struct TALER_MERCHANT_PostOrdersAbortedCoin *aborts;
+
+ } bad_gateway;
+
+ /**
+ * Details on #MHD_HTTP_GATEWAY_TIMEOUT.
+ */
+ struct
+ {
+
+ /**
+ * Taler error code from the exchange, if available.
+ */
+ enum TALER_ErrorCode exchange_ec;
+
+ /**
+ * HTTP status code returned by the exchange, if available.
+ * 0 if no exchange was involved or the request timed out
+ * before a response was received.
+ */
+ unsigned int exchange_http_status;
+
+ } gateway_timeout;
+
} details;
};
diff --git a/src/include/taler/taler-merchant/post-orders-ORDER_ID-pay.h b/src/include/taler/taler-merchant/post-orders-ORDER_ID-pay.h
@@ -266,7 +266,12 @@ enum TALER_MERCHANT_PostOrdersPayOption
/**
* Output donation receipts.
*/
- TALER_MERCHANT_POST_ORDERS_PAY_OPTION_OUTPUT_DONAU
+ TALER_MERCHANT_POST_ORDERS_PAY_OPTION_OUTPUT_DONAU,
+
+ /**
+ * Payment choice index (wallet mode).
+ */
+ TALER_MERCHANT_POST_ORDERS_PAY_OPTION_CHOICE_INDEX
};
@@ -372,6 +377,12 @@ struct TALER_MERCHANT_PostOrdersPayOptionValue
uint64_t year;
} output_donau;
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_POST_ORDERS_PAY_OPTION_CHOICE_INDEX.
+ */
+ int choice_index;
+
} details;
};
@@ -429,6 +440,32 @@ struct TALER_MERCHANT_PostOrdersPayResponse
} ok;
/**
+ * Details on #MHD_HTTP_CONFLICT.
+ * Set when a coin was already spent (insufficient funds) or
+ * the order was already fully paid.
+ */
+ struct
+ {
+
+ /**
+ * Base URL of the exchange that reported the conflict,
+ * or NULL if not applicable.
+ */
+ const char *exchange_url;
+
+ /**
+ * Taler error code from the exchange, if available.
+ */
+ enum TALER_ErrorCode exchange_ec;
+
+ /**
+ * HTTP status code returned by the exchange, if available.
+ */
+ unsigned int exchange_http_status;
+
+ } conflict;
+
+ /**
* Details on #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
*/
struct
@@ -446,6 +483,31 @@ struct TALER_MERCHANT_PostOrdersPayResponse
} unavailable_for_legal_reasons;
+ /**
+ * Details on #MHD_HTTP_BAD_GATEWAY.
+ * The merchant's interaction with the exchange failed.
+ */
+ struct
+ {
+
+ /**
+ * Base URL of the exchange that returned an error,
+ * or NULL if not available.
+ */
+ const char *exchange_url;
+
+ /**
+ * Taler error code from the exchange, if available.
+ */
+ enum TALER_ErrorCode exchange_ec;
+
+ /**
+ * HTTP status code returned by the exchange, if available.
+ */
+ unsigned int exchange_http_status;
+
+ } bad_gateway;
+
} details;
};
@@ -563,6 +625,20 @@ struct TALER_MERCHANT_PostOrdersPayResponse
.details.output_donau.year = (y) \
}
+/**
+ * Set payment choice index (wallet mode).
+ * Use -1 to indicate no choice (the default).
+ *
+ * @param i choice index
+ * @return representation of the option
+ */
+#define TALER_MERCHANT_post_orders_pay_option_choice_index(i) \
+ (const struct TALER_MERCHANT_PostOrdersPayOptionValue) \
+ { \
+ .option = TALER_MERCHANT_POST_ORDERS_PAY_OPTION_CHOICE_INDEX, \
+ .details.choice_index = (i) \
+ }
+
/**
* Set the requested options for the operation.
@@ -631,7 +707,6 @@ TALER_MERCHANT_post_orders_pay_frontend_create (
* @param url base URL of the merchant backend
* @param order_id identifier of the order to pay
* @param h_contract_terms hash of the contract terms
- * @param choice_index payment choice index
* @param amount total payment amount
* @param max_fee maximum acceptable fee
* @param merchant_pub merchant public key
@@ -650,7 +725,6 @@ TALER_MERCHANT_post_orders_pay_create (
const char *url,
const char *order_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
- int choice_index, // fIXME: make this an option?
const struct TALER_Amount *amount,
const struct TALER_Amount *max_fee,
const struct TALER_MerchantPublicKeyP *merchant_pub,
diff --git a/src/lib/merchant_api_get-orders-ORDER_ID.c b/src/lib/merchant_api_get-orders-ORDER_ID.c
@@ -77,6 +77,11 @@ struct TALER_MERCHANT_GetOrdersHandle
struct TALER_PrivateContractHashP h_contract;
/**
+ * Claim token for unclaimed order authentication.
+ */
+ struct TALER_ClaimTokenP token;
+
+ /**
* Session ID for repurchase detection, or NULL.
*/
char *session_id;
@@ -92,6 +97,21 @@ struct TALER_MERCHANT_GetOrdersHandle
struct TALER_Amount min_refund;
/**
+ * Whether refunded orders count for repurchase detection.
+ */
+ enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
+
+ /**
+ * True if @e h_contract was set.
+ */
+ bool have_h_contract;
+
+ /**
+ * True if @e token was set.
+ */
+ bool have_token;
+
+ /**
* True if @e min_refund was set.
*/
bool have_min_refund;
@@ -219,6 +239,10 @@ handle_get_order_finished (void *cls,
TALER_MERCHANT_get_orders_cancel (oph);
return;
}
+ case MHD_HTTP_BAD_REQUEST:
+ owgr.hr.ec = TALER_JSON_get_error_code (json);
+ owgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
case MHD_HTTP_FORBIDDEN:
owgr.hr.ec = TALER_JSON_get_error_code (json);
owgr.hr.hint = TALER_JSON_get_error_hint (json);
@@ -231,6 +255,14 @@ handle_get_order_finished (void *cls,
owgr.hr.ec = TALER_JSON_get_error_code (json);
owgr.hr.hint = TALER_JSON_get_error_hint (json);
break;
+ case MHD_HTTP_CONFLICT:
+ owgr.hr.ec = TALER_JSON_get_error_code (json);
+ owgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ owgr.hr.ec = TALER_JSON_get_error_code (json);
+ owgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
default:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
@@ -251,8 +283,7 @@ struct TALER_MERCHANT_GetOrdersHandle *
TALER_MERCHANT_get_orders_create (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const char *order_id,
- const struct TALER_PrivateContractHashP *h_contract)
+ const char *order_id)
{
struct TALER_MERCHANT_GetOrdersHandle *oph;
@@ -260,7 +291,7 @@ TALER_MERCHANT_get_orders_create (
oph->ctx = ctx;
oph->base_url = GNUNET_strdup (url);
oph->order_id = GNUNET_strdup (order_id);
- oph->h_contract = *h_contract;
+ oph->allow_refunded_for_repurchase = TALER_EXCHANGE_YNA_NO;
return oph;
}
@@ -295,6 +326,18 @@ TALER_MERCHANT_get_orders_set_options_ (
case TALER_MERCHANT_GET_ORDERS_OPTION_AWAIT_REFUND_OBTAINED:
oph->await_refund_obtained = opt->details.await_refund_obtained;
break;
+ case TALER_MERCHANT_GET_ORDERS_OPTION_H_CONTRACT:
+ oph->h_contract = opt->details.h_contract;
+ oph->have_h_contract = true;
+ break;
+ case TALER_MERCHANT_GET_ORDERS_OPTION_TOKEN:
+ oph->token = opt->details.token;
+ oph->have_token = true;
+ break;
+ case TALER_MERCHANT_GET_ORDERS_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE:
+ oph->allow_refunded_for_repurchase
+ = opt->details.allow_refunded_for_repurchase;
+ break;
default:
GNUNET_break (0);
return GNUNET_NO;
@@ -319,11 +362,18 @@ TALER_MERCHANT_get_orders_start (
/ GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
{
struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s;
+ char *token_s = NULL;
char *path;
char timeout_ms[32];
-
- GNUNET_CRYPTO_hash_to_enc (&oph->h_contract.hash,
- &h_contract_s);
+ const char *yna_s;
+
+ if (oph->have_h_contract)
+ GNUNET_CRYPTO_hash_to_enc (&oph->h_contract.hash,
+ &h_contract_s);
+ if (oph->have_token)
+ token_s = GNUNET_STRINGS_data_to_string_alloc (
+ &oph->token,
+ sizeof (oph->token));
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
"%u",
@@ -331,10 +381,17 @@ TALER_MERCHANT_get_orders_start (
GNUNET_asprintf (&path,
"orders/%s",
oph->order_id);
+ yna_s = (TALER_EXCHANGE_YNA_NO != oph->allow_refunded_for_repurchase)
+ ? TALER_yna_to_string (oph->allow_refunded_for_repurchase)
+ : NULL;
oph->url = TALER_url_join (oph->base_url,
path,
"h_contract",
- h_contract_s.encoding,
+ oph->have_h_contract
+ ? h_contract_s.encoding
+ : NULL,
+ "token",
+ token_s,
"session_id",
oph->session_id,
"timeout_ms",
@@ -349,8 +406,11 @@ TALER_MERCHANT_get_orders_start (
oph->await_refund_obtained
? "yes"
: NULL,
+ "allow_refunded_for_repurchase",
+ yna_s,
NULL);
GNUNET_free (path);
+ GNUNET_free (token_s);
}
if (NULL == oph->url)
return TALER_EC_GENERIC_CONFIGURATION_INVALID;
diff --git a/src/lib/merchant_api_post-orders-ORDER_ID-abort.c b/src/lib/merchant_api_post-orders-ORDER_ID-abort.c
@@ -203,8 +203,19 @@ check_abort_refund (struct TALER_MERCHANT_PostOrdersAbortHandle *poah,
}
}
}
- ar->details.ok.num_aborts = num_refunds;
- ar->details.ok.aborts = res;
+ switch (ar->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ ar->details.ok.num_aborts = num_refunds;
+ ar->details.ok.aborts = res;
+ break;
+ case MHD_HTTP_BAD_GATEWAY:
+ ar->details.bad_gateway.num_aborts = num_refunds;
+ ar->details.bad_gateway.aborts = res;
+ break;
+ default:
+ GNUNET_assert (0);
+ }
poah->cb (poah->cb_cls,
ar);
poah->cb = NULL;
@@ -279,13 +290,24 @@ handle_abort_finished (void *cls,
ar.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_BAD_GATEWAY:
+ if (GNUNET_OK ==
+ check_abort_refund (poah,
+ &ar,
+ json))
+ {
+ TALER_MERCHANT_post_orders_abort_cancel (poah);
+ return;
+ }
+ ar.hr.http_status = 0;
+ ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_GATEWAY_TIMEOUT:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
&ar.hr);
- break;
- case MHD_HTTP_GATEWAY_TIMEOUT:
- ar.hr.ec = TALER_JSON_get_error_code (json);
- ar.hr.hint = TALER_JSON_get_error_hint (json);
+ ar.details.gateway_timeout.exchange_ec = ar.hr.exchange_code;
+ ar.details.gateway_timeout.exchange_http_status
+ = ar.hr.exchange_http_status;
break;
default:
TALER_MERCHANT_parse_error_details_ (json,
diff --git a/src/lib/merchant_api_post-orders-ORDER_ID-pay.c b/src/lib/merchant_api_post-orders-ORDER_ID-pay.c
@@ -379,6 +379,15 @@ handle_pay_finished (void *cls,
TALER_MERCHANT_parse_error_details_ (json,
MHD_HTTP_CONFLICT,
&pr.hr);
+ {
+ const char *eu = json_string_value (
+ json_object_get (json, "exchange_url"));
+
+ pr.details.conflict.exchange_url = eu;
+ pr.details.conflict.exchange_ec = pr.hr.exchange_code;
+ pr.details.conflict.exchange_http_status
+ = pr.hr.exchange_http_status;
+ }
break;
case MHD_HTTP_GONE:
TALER_MERCHANT_parse_error_details_ (json,
@@ -452,6 +461,15 @@ handle_pay_finished (void *cls,
TALER_MERCHANT_parse_error_details_ (json,
response_code,
&pr.hr);
+ {
+ const char *eu = json_string_value (
+ json_object_get (json, "exchange_url"));
+
+ pr.details.bad_gateway.exchange_url = eu;
+ pr.details.bad_gateway.exchange_ec = pr.hr.exchange_code;
+ pr.details.bad_gateway.exchange_http_status
+ = pr.hr.exchange_http_status;
+ }
break;
case MHD_HTTP_GATEWAY_TIMEOUT:
TALER_MERCHANT_parse_error_details_ (json,
@@ -517,7 +535,6 @@ TALER_MERCHANT_post_orders_pay_create (
const char *url,
const char *order_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
- int choice_index,
const struct TALER_Amount *amount,
const struct TALER_Amount *max_fee,
const struct TALER_MerchantPublicKeyP *merchant_pub,
@@ -545,7 +562,7 @@ TALER_MERCHANT_post_orders_pay_create (
poph->order_id = GNUNET_strdup (order_id);
poph->am_wallet = true;
poph->h_contract_terms = *h_contract_terms;
- poph->choice_index = choice_index;
+ poph->choice_index = -1;
poph->amount = *amount;
poph->max_fee = *max_fee;
poph->merchant_pub = *merchant_pub;
@@ -675,6 +692,9 @@ TALER_MERCHANT_post_orders_pay_set_options_ (
src->blinded_udi.blinded_message);
}
break;
+ case TALER_MERCHANT_POST_ORDERS_PAY_OPTION_CHOICE_INDEX:
+ poph->choice_index = options[i].details.choice_index;
+ break;
default:
GNUNET_break (0);
return GNUNET_SYSERR;
diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c
@@ -1372,7 +1372,6 @@ pay_run (void *cls,
ps->merchant_url,
order_id,
h_proposal,
- ps->choice_index,
&ps->total_amount,
&max_fee,
&merchant_pub,
@@ -1386,6 +1385,13 @@ pay_run (void *cls,
if (NULL == ps->oph)
TALER_TESTING_FAIL (is);
+ if (0 <= ps->choice_index)
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_MERCHANT_post_orders_pay_set_options (
+ ps->oph,
+ TALER_MERCHANT_post_orders_pay_option_choice_index (
+ ps->choice_index)));
if (NULL != ps->session_id)
GNUNET_assert (
GNUNET_OK ==
diff --git a/src/testing/testing_api_cmd_wallet_get_order.c b/src/testing/testing_api_cmd_wallet_get_order.c
@@ -278,12 +278,13 @@ wallet_get_order_run (void *cls,
gos->ogh = TALER_MERCHANT_get_orders_create (
TALER_TESTING_interpreter_get_context (is),
gos->merchant_url,
- order_id,
- h_contract);
- if (NULL != gos->session_id)
+ order_id);
+ GNUNET_assert (
+ GNUNET_OK ==
TALER_MERCHANT_get_orders_set_options (
gos->ogh,
- TALER_MERCHANT_get_orders_option_session_id (gos->session_id));
+ TALER_MERCHANT_get_orders_option_h_contract (*h_contract),
+ TALER_MERCHANT_get_orders_option_session_id (gos->session_id)));
{
enum TALER_ErrorCode ec;
@@ -685,8 +686,10 @@ wallet_poll_order_start_run (void *cls,
pos->ogh = TALER_MERCHANT_get_orders_create (
TALER_TESTING_interpreter_get_context (is),
pos->merchant_url,
- order_id,
- h_contract);
+ order_id);
+ TALER_MERCHANT_get_orders_set_options (
+ pos->ogh,
+ TALER_MERCHANT_get_orders_option_h_contract (*h_contract));
TALER_MERCHANT_get_orders_set_options (
pos->ogh,
TALER_MERCHANT_get_orders_option_timeout (pos->timeout),