commit 3041f1111e5878b02c7ec48999966fd7c1a047d3
parent 93465baaf1a765b3fe66a61a0f6d0de551757abd
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 25 Mar 2026 23:07:33 +0100
more API cleanups
Diffstat:
12 files changed, 519 insertions(+), 1100 deletions(-)
diff --git a/src/include/taler/taler-merchant/common.h b/src/include/taler/taler-merchant/common.h
@@ -257,12 +257,25 @@ struct TALER_MERCHANT_WireTransfer
struct TALER_Amount total_amount;
/**
+ * Deposit fees paid to the exchange for this order.
+ * @since protocol v26.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
* Whether this transfer was confirmed by the merchant using
* the POST /transfers API, or whether it was merely claimed
* by the exchange.
*/
bool confirmed;
+ /**
+ * Transfer serial ID of this wire transfer, useful as
+ * offset for the GET /private/incoming endpoint.
+ * @since protocol v25.
+ */
+ uint64_t expected_transfer_serial_id;
+
};
diff --git a/src/include/taler/taler-merchant/get-orders-ORDER_ID.h b/src/include/taler/taler-merchant/get-orders-ORDER_ID.h
@@ -287,6 +287,20 @@ struct TALER_MERCHANT_GetOrdersResponse
} ok;
/**
+ * Details on #MHD_HTTP_ACCEPTED (order claimed, goto reorder URL).
+ */
+ struct
+ {
+
+ /**
+ * URL where the client should go to create a fresh order
+ * or trigger repurchase detection.
+ */
+ const char *public_reorder_url;
+
+ } accepted;
+
+ /**
* Details on #MHD_HTTP_PAYMENT_REQUIRED (order not yet paid).
*/
struct
diff --git a/src/include/taler/taler-merchant/get-private-orders-ORDER_ID.h b/src/include/taler/taler-merchant/get-private-orders-ORDER_ID.h
@@ -43,7 +43,28 @@ enum TALER_MERCHANT_GetPrivateOrderOption
/**
* Long polling timeout.
*/
- TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TIMEOUT
+ TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TIMEOUT,
+
+ /**
+ * If set to true, try to obtain the wire transfer status
+ * for this order from the exchange.
+ * @deprecated since protocol v6.
+ */
+ TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TRANSFER,
+
+ /**
+ * Long-poll ETag to suppress responses that have not changed.
+ * ShortHashCode value.
+ * @since protocol v25.
+ */
+ TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_LP_NOT_ETAG,
+
+ /**
+ * If true, refunded orders may be returned under
+ * "already_paid_order_id".
+ * @since protocol v9.
+ */
+ TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE
};
@@ -77,6 +98,24 @@ struct TALER_MERCHANT_GetPrivateOrderOptionValue
*/
struct GNUNET_TIME_Relative timeout;
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TRANSFER.
+ */
+ bool transfer;
+
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_LP_NOT_ETAG.
+ */
+ const struct GNUNET_ShortHashCode *lp_not_etag;
+
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE.
+ */
+ bool allow_refunded_for_repurchase;
+
} details;
};
@@ -142,6 +181,46 @@ TALER_MERCHANT_get_private_order_create (
.details.timeout = (t) \
}
+/**
+ * Set transfer flag (try to obtain wire transfer status from exchange).
+ * @deprecated since protocol v6.
+ *
+ * @param b true to request wire transfer status
+ * @return representation of the option as a struct TALER_MERCHANT_GetPrivateOrderOptionValue
+ */
+#define TALER_MERCHANT_get_private_order_option_transfer(b) \
+ (const struct TALER_MERCHANT_GetPrivateOrderOptionValue) \
+ { \
+ .option = TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TRANSFER, \
+ .details.transfer = (b) \
+ }
+
+/**
+ * Set long-poll ETag to suppress unchanged responses.
+ *
+ * @param e pointer to the short hash code ETag
+ * @return representation of the option as a struct TALER_MERCHANT_GetPrivateOrderOptionValue
+ */
+#define TALER_MERCHANT_get_private_order_option_lp_not_etag(e) \
+ (const struct TALER_MERCHANT_GetPrivateOrderOptionValue) \
+ { \
+ .option = TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_LP_NOT_ETAG, \
+ .details.lp_not_etag = (e) \
+ }
+
+/**
+ * Set allow-refunded-for-repurchase flag.
+ *
+ * @param b true to allow refunded orders under already_paid_order_id
+ * @return representation of the option as a struct TALER_MERCHANT_GetPrivateOrderOptionValue
+ */
+#define TALER_MERCHANT_get_private_order_option_allow_refunded_for_repurchase(b) \
+ (const struct TALER_MERCHANT_GetPrivateOrderOptionValue) \
+ { \
+ .option = TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE, \
+ .details.allow_refunded_for_repurchase = (b) \
+ }
+
/**
* Set the requested options for the operation.
@@ -232,6 +311,12 @@ struct TALER_MERCHANT_GetPrivateOrderRefundDetail
*/
struct TALER_Amount refund_amount;
+ /**
+ * True if a refund is still available for the wallet
+ * for this payment.
+ */
+ bool pending;
+
};
@@ -276,17 +361,17 @@ struct TALER_MERCHANT_GetPrivateOrderResponse
{
/**
- * Total amount of the order.
- */
- struct TALER_Amount contract_amount;
-
- /**
* Order ID of a previously paid order that covers this one,
* or NULL if none.
*/
const char *already_paid_order_id;
/**
+ * Fulfillment URL of an already paid order, or NULL.
+ */
+ const char *already_paid_fulfillment_url;
+
+ /**
* Taler pay URI for this order.
*/
const char *taler_pay_uri;
@@ -297,10 +382,27 @@ struct TALER_MERCHANT_GetPrivateOrderResponse
const char *summary;
/**
+ * Proto contract terms (contract terms without nonce).
+ * @since protocol v25.
+ */
+ const json_t *proto_contract_terms;
+
+ /**
+ * Status URL for the order.
+ */
+ const char *order_status_url;
+
+ /**
* Timestamp when the order was created.
*/
struct GNUNET_TIME_Timestamp creation_time;
+ /**
+ * Deadline when the offer expires.
+ * @since protocol v21.
+ */
+ struct GNUNET_TIME_Timestamp pay_deadline;
+
} unpaid;
/**
@@ -314,6 +416,12 @@ struct TALER_MERCHANT_GetPrivateOrderResponse
*/
const json_t *contract_terms;
+ /**
+ * Status URL for the order.
+ * @since protocol v19.
+ */
+ const char *order_status_url;
+
} claimed;
/**
@@ -387,6 +495,18 @@ struct TALER_MERCHANT_GetPrivateOrderResponse
*/
struct GNUNET_TIME_Timestamp last_payment;
+ /**
+ * Status URL for the order.
+ */
+ const char *order_status_url;
+
+ /**
+ * Index of the selected choice within the choices array
+ * of contract terms, or -1 if not set.
+ * @since protocol v21.
+ */
+ int choice_index;
+
} paid;
} details;
diff --git a/src/include/taler/taler-merchant/get-private-orders.h b/src/include/taler/taler-merchant/get-private-orders.h
@@ -85,7 +85,14 @@ enum TALER_MERCHANT_GetPrivateOrdersOption
/**
* Filter by summary text (case-insensitive substring match).
*/
- TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SUMMARY_FILTER
+ TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_SUMMARY_FILTER,
+
+ /**
+ * Only return orders younger than the specified age.
+ * Only applicable if limit is positive.
+ * @since protocol v27.
+ */
+ TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_MAX_AGE
};
@@ -167,6 +174,12 @@ struct TALER_MERCHANT_GetPrivateOrdersOptionValue
*/
const char *summary_filter;
+ /**
+ * Value if @e option is
+ * #TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_MAX_AGE.
+ */
+ struct GNUNET_TIME_Relative max_age;
+
} details;
};
@@ -334,6 +347,21 @@ TALER_MERCHANT_get_private_orders_create (
.details.summary_filter = (f) \
}
+/**
+ * Set maximum age filter. Only return orders younger than the
+ * specified age. Only applicable if limit is positive.
+ * @since protocol v27.
+ *
+ * @param a maximum age as a relative time
+ * @return representation of the option as a struct TALER_MERCHANT_GetPrivateOrdersOptionValue
+ */
+#define TALER_MERCHANT_get_private_orders_option_max_age(a) \
+ (const struct TALER_MERCHANT_GetPrivateOrdersOptionValue) \
+ { \
+ .option = TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_MAX_AGE, \
+ .details.max_age = (a) \
+ }
+
/**
* Set the requested options for the operation.
diff --git a/src/include/taler/taler-merchant/post-private-orders.h b/src/include/taler/taler-merchant/post-private-orders.h
@@ -47,6 +47,13 @@ struct TALER_MERCHANT_PostPrivateOrdersInventoryProduct
uint32_t quantity_frac;
/**
+ * Money pot to use for this product, overrides value from
+ * the inventory if given. -1 if not set.
+ * @since protocol v25.
+ */
+ int32_t product_money_pot;
+
+ /**
* Whether to use fractional quantity.
*/
bool use_fractional_quantity;
@@ -181,6 +188,31 @@ struct TALER_MERCHANT_PostPrivateOrdersHandle;
/**
+ * Detail about why a specific exchange was rejected when
+ * creating an order.
+ */
+struct TALER_MERCHANT_ExchangeRejectionDetail
+{
+
+ /**
+ * Base URL of the rejected exchange.
+ */
+ const char *exchange_url;
+
+ /**
+ * Error code indicating why this exchange was not acceptable.
+ */
+ enum TALER_ErrorCode code;
+
+ /**
+ * Human-readable hint about the error, or NULL.
+ */
+ const char *hint;
+
+};
+
+
+/**
* Response details for a POST /private/orders request.
*/
struct TALER_MERCHANT_PostPrivateOrdersResponse
@@ -252,12 +284,44 @@ struct TALER_MERCHANT_PostPrivateOrdersResponse
uint32_t available_quantity_frac;
/**
+ * Requested quantity as string in "<integer>[.<fraction>]" format,
+ * or NULL if not provided.
+ */
+ const char *unit_requested_quantity;
+
+ /**
+ * Available quantity as string in "<integer>[.<fraction>]" format,
+ * or NULL if not provided.
+ */
+ const char *unit_available_quantity;
+
+ /**
* Expected restock time.
*/
struct GNUNET_TIME_Timestamp restock_expected;
} gone;
+ /**
+ * Details on #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+ * @since protocol v25.
+ */
+ struct
+ {
+
+ /**
+ * Number of entries in @a exchange_rejections.
+ */
+ unsigned int num_exchange_rejections;
+
+ /**
+ * Array of exchange rejection details.
+ */
+ const struct TALER_MERCHANT_ExchangeRejectionDetail
+ *exchange_rejections;
+
+ } unavailable_for_legal_reasons;
+
} details;
};
diff --git a/src/lib/merchant_api_get-orders-ORDER_ID.c b/src/lib/merchant_api_get-orders-ORDER_ID.c
@@ -27,6 +27,7 @@
#include <gnunet/gnunet_curl_lib.h>
#include <taler/taler-merchant/get-orders-ORDER_ID.h>
#include "merchant_api_curl_defaults.h"
+#include "merchant_api_common.h"
#include <taler/taler_json_lib.h>
@@ -158,6 +159,33 @@ handle_get_order_finished (void *cls,
TALER_MERCHANT_get_orders_cancel (oph);
return;
}
+ case MHD_HTTP_ACCEPTED:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string (
+ "public_reorder_url",
+ &owgr.details.accepted.public_reorder_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ owgr.hr.http_status = 0;
+ owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ oph->cb (oph->cb_cls,
+ &owgr);
+ TALER_MERCHANT_get_orders_cancel (oph);
+ return;
+ }
+ case MHD_HTTP_FOUND:
+ /* Redirect; Location header has the target URL.
+ No JSON body expected. */
+ break;
case MHD_HTTP_PAYMENT_REQUIRED:
{
struct GNUNET_JSON_Specification spec[] = {
@@ -191,9 +219,22 @@ handle_get_order_finished (void *cls,
TALER_MERCHANT_get_orders_cancel (oph);
return;
}
- default:
+ case MHD_HTTP_FORBIDDEN:
owgr.hr.ec = TALER_JSON_get_error_code (json);
owgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ owgr.hr.ec = TALER_JSON_get_error_code (json);
+ owgr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_ACCEPTABLE:
+ 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,
+ &owgr.hr);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
diff --git a/src/lib/merchant_api_get-private-kyc.c b/src/lib/merchant_api_get-private-kyc.c
@@ -389,10 +389,25 @@ handle_get_kyc_finished (void *cls,
}
case MHD_HTTP_NO_CONTENT:
break;
+ case MHD_HTTP_NOT_MODIFIED:
+ /* ETag matched; nothing changed. No body expected. */
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ kr.hr.ec = TALER_JSON_get_error_code (json);
+ kr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
case MHD_HTTP_UNAUTHORIZED:
kr.hr.ec = TALER_JSON_get_error_code (json);
kr.hr.hint = TALER_JSON_get_error_hint (json);
break;
+ case MHD_HTTP_NOT_FOUND:
+ kr.hr.ec = TALER_JSON_get_error_code (json);
+ kr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_ACCEPTABLE:
+ kr.hr.ec = TALER_JSON_get_error_code (json);
+ kr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
case MHD_HTTP_SERVICE_UNAVAILABLE:
break;
default:
diff --git a/src/lib/merchant_api_get-private-orders-ORDER_ID.c b/src/lib/merchant_api_get-private-orders-ORDER_ID.c
@@ -90,6 +90,26 @@ struct TALER_MERCHANT_GetPrivateOrderHandle
* Long polling timeout.
*/
struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Long-poll ETag to suppress unchanged responses.
+ */
+ struct GNUNET_ShortHashCode lp_not_etag;
+
+ /**
+ * True if @e lp_not_etag was set.
+ */
+ bool have_lp_not_etag;
+
+ /**
+ * If true, try to obtain wire transfer status from the exchange.
+ */
+ bool transfer;
+
+ /**
+ * If true, allow refunded orders under already_paid_order_id.
+ */
+ bool allow_refunded_for_repurchase;
};
@@ -104,23 +124,38 @@ handle_unpaid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
{
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any (
- "total_amount",
- &osr->details.ok.details.unpaid.contract_amount),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string (
"already_paid_order_id",
&osr->details.ok.details.unpaid.already_paid_order_id),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "already_paid_fulfillment_url",
+ &osr->details.ok.details.unpaid.already_paid_fulfillment_url),
+ NULL),
GNUNET_JSON_spec_string (
"taler_pay_uri",
&osr->details.ok.details.unpaid.taler_pay_uri),
GNUNET_JSON_spec_string (
"summary",
&osr->details.ok.details.unpaid.summary),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const (
+ "proto_contract_terms",
+ &osr->details.ok.details.unpaid.proto_contract_terms),
+ NULL),
+ GNUNET_JSON_spec_string (
+ "order_status_url",
+ &osr->details.ok.details.unpaid.order_status_url),
GNUNET_JSON_spec_timestamp (
"creation_time",
&osr->details.ok.details.unpaid.creation_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp (
+ "pay_deadline",
+ &osr->details.ok.details.unpaid.pay_deadline),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -156,6 +191,11 @@ handle_claimed (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
GNUNET_JSON_spec_object_const (
"contract_terms",
&osr->details.ok.details.claimed.contract_terms),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "order_status_url",
+ &osr->details.ok.details.claimed.order_status_url),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -188,6 +228,8 @@ handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
{
uint32_t hc32;
+ uint32_t ci32;
+ bool ci_missing;
const json_t *wire_details;
const json_t *refund_details;
struct GNUNET_JSON_Specification spec[] = {
@@ -208,6 +250,10 @@ handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
GNUNET_JSON_spec_object_const (
"contract_terms",
&osr->details.ok.details.paid.contract_terms),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("choice_index",
+ &ci32),
+ &ci_missing),
GNUNET_JSON_spec_array_const ("wire_details",
&wire_details),
GNUNET_JSON_spec_array_const ("refund_details",
@@ -216,6 +262,9 @@ handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
GNUNET_JSON_spec_timestamp ("last_payment",
&osr->details.ok.details.paid.last_payment),
NULL),
+ GNUNET_JSON_spec_string (
+ "order_status_url",
+ &osr->details.ok.details.paid.order_status_url),
GNUNET_JSON_spec_end ()
};
@@ -233,6 +282,9 @@ handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
}
osr->details.ok.status = TALER_MERCHANT_OSC_PAID;
osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32;
+ osr->details.ok.details.paid.choice_index = ci_missing
+ ? -1
+ : (int) ci32;
{
unsigned int wts_len = (unsigned int) json_array_size (wire_details);
unsigned int ref_len = (unsigned int) json_array_size (refund_details);
@@ -276,8 +328,16 @@ handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
&wt->execution_time),
TALER_JSON_spec_amount_any ("amount",
&wt->total_amount),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("deposit_fee",
+ &wt->deposit_fee),
+ NULL),
GNUNET_JSON_spec_bool ("confirmed",
&wt->confirmed),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("expected_transfer_serial_id",
+ &wt->expected_transfer_serial_id),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -305,6 +365,8 @@ handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
&ro->refund_amount),
GNUNET_JSON_spec_string ("reason",
&ro->reason),
+ GNUNET_JSON_spec_bool ("pending",
+ &ro->pending),
GNUNET_JSON_spec_timestamp ("timestamp",
&ro->refund_time),
GNUNET_JSON_spec_end ()
@@ -492,6 +554,20 @@ TALER_MERCHANT_get_private_order_set_options_ (
case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TIMEOUT:
oph->timeout = opt->details.timeout;
break;
+ case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TRANSFER:
+ oph->transfer = opt->details.transfer;
+ break;
+ case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_LP_NOT_ETAG:
+ if (NULL != opt->details.lp_not_etag)
+ {
+ oph->lp_not_etag = *opt->details.lp_not_etag;
+ oph->have_lp_not_etag = true;
+ }
+ break;
+ case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE:
+ oph->allow_refunded_for_repurchase
+ = opt->details.allow_refunded_for_repurchase;
+ break;
default:
GNUNET_break (0);
return GNUNET_NO;
@@ -517,11 +593,23 @@ TALER_MERCHANT_get_private_order_start (
{
char *path;
char timeout_ms[32];
+ char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1];
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
"%u",
tms);
+ if (oph->have_lp_not_etag)
+ {
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &oph->lp_not_etag,
+ sizeof (oph->lp_not_etag),
+ etag_str,
+ sizeof (etag_str) - 1);
+ *end = '\0';
+ }
GNUNET_asprintf (&path,
"private/orders/%s",
oph->order_id);
@@ -533,6 +621,18 @@ TALER_MERCHANT_get_private_order_start (
(0 != tms)
? timeout_ms
: NULL,
+ "transfer",
+ oph->transfer
+ ? "YES"
+ : NULL,
+ "lp_not_etag",
+ oph->have_lp_not_etag
+ ? etag_str
+ : NULL,
+ "allow_refunded_for_repurchase",
+ oph->allow_refunded_for_repurchase
+ ? "YES"
+ : NULL,
NULL);
GNUNET_free (path);
}
diff --git a/src/lib/merchant_api_get-private-orders.c b/src/lib/merchant_api_get-private-orders.c
@@ -122,6 +122,12 @@ struct TALER_MERCHANT_GetPrivateOrdersHandle
char *summary_filter;
/**
+ * Maximum age filter.
+ * @since protocol v27.
+ */
+ struct GNUNET_TIME_Relative max_age;
+
+ /**
* True if offset was explicitly set.
*/
bool have_offset;
@@ -130,6 +136,11 @@ struct TALER_MERCHANT_GetPrivateOrdersHandle
* True if date was explicitly set.
*/
bool have_date;
+
+ /**
+ * True if @e max_age was set.
+ */
+ bool have_max_age;
};
@@ -362,6 +373,10 @@ TALER_MERCHANT_get_private_orders_set_options_ (
if (NULL != opt->details.summary_filter)
oph->summary_filter = GNUNET_strdup (opt->details.summary_filter);
break;
+ case TALER_MERCHANT_GET_PRIVATE_ORDERS_OPTION_MAX_AGE:
+ oph->max_age = opt->details.max_age;
+ oph->have_max_age = true;
+ break;
default:
GNUNET_break (0);
return GNUNET_NO;
@@ -394,11 +409,18 @@ TALER_MERCHANT_get_private_orders_start (
char cbuf[30];
char dbuf[30];
char tbuf[30];
+ char mabuf[30];
GNUNET_snprintf (tbuf,
sizeof (tbuf),
"%u",
tms);
+ if (oph->have_max_age)
+ GNUNET_snprintf (mabuf,
+ sizeof (mabuf),
+ "%llu",
+ (unsigned long long)
+ oph->max_age.rel_value_us);
GNUNET_snprintf (dbuf,
sizeof (dbuf),
"%lld",
@@ -474,6 +496,10 @@ TALER_MERCHANT_get_private_orders_start (
fec,
"summary_filter",
sfilt,
+ "max_age",
+ oph->have_max_age
+ ? mabuf
+ : NULL,
NULL);
GNUNET_free (sid);
GNUNET_free (fec);
diff --git a/src/lib/merchant_api_post-orders-ORDER_ID-pay.c b/src/lib/merchant_api_post-orders-ORDER_ID-pay.c
@@ -449,6 +449,11 @@ handle_pay_finished (void *cls,
response_code,
&pr.hr);
break;
+ case MHD_HTTP_NOT_IMPLEMENTED:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ break;
case MHD_HTTP_BAD_GATEWAY:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
diff --git a/src/lib/merchant_api_post-private-orders.c b/src/lib/merchant_api_post-private-orders.c
@@ -236,6 +236,16 @@ handle_post_orders_finished (void *cls,
&por.details.gone.available_quantity_frac),
&aq_frac_missing),
GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "unit_requested_quantity",
+ &por.details.gone.unit_requested_quantity),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "unit_available_quantity",
+ &por.details.gone.unit_available_quantity),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp (
"restock_expected",
&por.details.gone.restock_expected),
@@ -262,9 +272,67 @@ handle_post_orders_finished (void *cls,
break;
}
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- por.hr.ec = TALER_JSON_get_error_code (json);
- por.hr.hint = TALER_JSON_get_error_hint (json);
- break;
+ {
+ const json_t *jer = NULL;
+
+ por.hr.ec = TALER_JSON_get_error_code (json);
+ por.hr.hint = TALER_JSON_get_error_hint (json);
+ jer = json_object_get (json,
+ "exchange_rejections");
+ if ( (NULL != jer) &&
+ json_is_array (jer) )
+ {
+ unsigned int rej_len = (unsigned int) json_array_size (jer);
+
+ if (json_array_size (jer) == (size_t) rej_len)
+ {
+ struct TALER_MERCHANT_ExchangeRejectionDetail rejs[
+ GNUNET_NZL (rej_len)];
+ bool ok = true;
+
+ memset (rejs, 0, sizeof (rejs));
+ for (unsigned int i = 0; i < rej_len; i++)
+ {
+ struct GNUNET_JSON_Specification rspec[] = {
+ TALER_JSON_spec_web_url (
+ "exchange_url",
+ &rejs[i].exchange_url),
+ TALER_JSON_spec_ec (
+ "code",
+ &rejs[i].code),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "hint",
+ &rejs[i].hint),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (jer, i),
+ rspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ if (ok)
+ {
+ por.details.unavailable_for_legal_reasons
+ .num_exchange_rejections = rej_len;
+ por.details.unavailable_for_legal_reasons
+ .exchange_rejections = rejs;
+ ppoh->cb (ppoh->cb_cls,
+ &por);
+ TALER_MERCHANT_post_private_orders_cancel (ppoh);
+ return;
+ }
+ }
+ }
+ break;
+ }
case MHD_HTTP_INTERNAL_SERVER_ERROR:
por.hr.ec = TALER_JSON_get_error_code (json);
por.hr.hint = TALER_JSON_get_error_hint (json);
@@ -395,13 +463,14 @@ TALER_MERCHANT_post_private_orders_start (
{
json_t *ip;
- if (ppoh->inventory_products[i].use_fractional_quantity)
{
char unit_quantity_buf[64];
TALER_MERCHANT_format_quantity_string (
ppoh->inventory_products[i].quantity,
- ppoh->inventory_products[i].quantity_frac,
+ ppoh->inventory_products[i].use_fractional_quantity
+ ? ppoh->inventory_products[i].quantity_frac
+ : 0,
unit_quantity_buf,
sizeof (unit_quantity_buf));
ip = GNUNET_JSON_PACK (
@@ -410,22 +479,16 @@ TALER_MERCHANT_post_private_orders_start (
GNUNET_JSON_pack_string ("unit_quantity",
unit_quantity_buf));
}
- else
+ if (ppoh->inventory_products[i].product_money_pot > 0)
{
- char unit_quantity_buf[64];
-
- TALER_MERCHANT_format_quantity_string (
- ppoh->inventory_products[i].quantity,
- 0,
- unit_quantity_buf,
- sizeof (unit_quantity_buf));
- ip = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("product_id",
- ppoh->inventory_products[i].product_id),
- GNUNET_JSON_pack_string ("unit_quantity",
- unit_quantity_buf));
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (
+ ip,
+ "product_money_pot",
+ json_integer (
+ ppoh->inventory_products[i].product_money_pot)));
}
- GNUNET_assert (NULL != ip);
GNUNET_assert (0 ==
json_array_append_new (ipa,
ip));
diff --git a/src/lib/taler_merchant_pay_service.c b/src/lib/taler_merchant_pay_service.c
@@ -1,1070 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2024 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 Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along with
- TALER; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler_merchant_pay_service.c
- * @brief Implementation of the the ideology
- * from the pay_service as copy of
- * merchant_api_post_order_pay.c
- * @author Bohdan Potuzhnyi
- */
-#include "taler/platform.h"
-#include <curl/curl.h>
-#include <gnunet/gnunet_common.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_merchant_service.h"
-#include "taler/taler_merchant_pay_service.h"
-#include "merchant_api_common.h"
-#include "merchant_api_curl_defaults.h"
-#include <stdio.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_curl_lib.h>
-
-/**
- * @brief A Pay Handle
- */
-struct TALER_MERCHANT_OrderPayHandle
-{
- /**
- * Reference to the GNUNET CURL execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * Callback to invoke with the payment result ("pay" mode).
- */
- TALER_MERCHANT_OrderPayCallback cb;
-
- /**
- * Closure data for @a cb.
- */
- TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *cb_cls;
-
- /* Mandatory scalars: */
-
- /**
- * Base URL of the merchant service.
- */
- char *merchant_url;
-
- /**
- * Identifier of the order being paid.
- */
- char *order_id;
-
- /**
- * Session identifier for this payment attempt.
- */
- char *session_id;
-
- /**
- * Timestamp when the payment request was created.
- */
- struct GNUNET_TIME_Timestamp timestamp;
-
- /**
- * Deadline after which refunds are no longer allowed.
- */
- struct GNUNET_TIME_Timestamp refund_deadline;
-
- /**
- * Wire hash for communicating payment details.
- */
- struct TALER_MerchantWireHashP h_wire;
-
- /**
- * Indicates whether @a h_wire has been set.
- */
- bool has_h_wire;
-
- /* Wallet mode fields: */
-
- /**
- * Indicates whether a contract hash was provided.
- */
- bool has_h_contract;
-
- /**
- * Hash of the private contract terms (wallet mode only).
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Indicates whether the merchant public key was provided.
- */
- bool has_merchant_pub;
-
- /**
- * Merchant’s public key for verifying signatures (wallet mode).
- */
- struct TALER_MerchantPublicKeyP merchant_pub;
-
- /**
- * Indicates whether a choice index was provided.
- */
- bool has_choice_index;
-
- /**
- * Selected index of the contract choice (for token operations).
- */
- int choice_index;
-
- /**
- * Legacy: pointer to the amount structure for strcmp checks.
- */
- const struct TALER_Amount *amount;
-
- /**
- * Legacy: pointer to the maximum fee structure for strcmp checks.
- */
- const struct TALER_Amount *max_fee;
-
- /* Raw arrays as passed in via set_options(): */
-
- /**
- * Coins used for payment.
- */
- struct
- {
- /**
- * Number of coins provided.
- */
- unsigned int num_coins;
- /**
- * Array of coins to spend.
- */
- const struct TALER_MERCHANT_PayCoin *coins;
- } coins;
-
- /**
- * Input tokens to use (wallet mode).
- */
- struct
- {
- /**
- * Number of tokens provided.
- */
- unsigned int num_tokens;
- /**
- * Array of tokens to redeem.
- */
- const struct TALER_MERCHANT_UseToken *tokens;
- } input_tokens;
-
- /**
- * Output tokens expected from the merchant.
- */
- struct
- {
- /**
- * Number of output tokens expected.
- */
- unsigned int num_output_tokens;
- /**
- * Array of expected output tokens.
- */
- const struct TALER_MERCHANT_OutputToken *output_tokens;
- } output_tokens;
-
- /**
- * JSON array of token envelope events (from Donau).
- */
- json_t *tokens_evs;
-
- /* Computed once both choice_index and tokens_evs are available: */
-
- /**
- * JSON object containing wallet-specific data payload.
- */
- json_t *wallet_data;
-
- /**
- * Hash code of @a wallet_data for integrity checks.
- */
- struct GNUNET_HashCode wallet_data_hash;
-
- /**
- * JSON body being constructed for the HTTP POST.
- */
- json_t *body;
-
- /* Final URL and CURL plumbing: */
-
- /**
- * Fully formed URL for the POST /order/$ID/pay request.
- */
- char *url;
-
- /**
- * CURL post context managing headers and body.
- */
- struct TALER_CURL_PostContext post_ctx;
-
- /**
- * Handle for the asynchronous CURL job.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Flags indicating which payment options have been set.
- */
- bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH];
-
- /**
- * True if operating in wallet mode (using tokens/contracts).
- */
- bool am_wallet;
-
- /**
- * Raw JSON data of `donau` for `wallet_data`.
- */
- json_t *donau_data;
-};
-
-/**
- * Parse blindly signed output tokens from response.
- *
- * @param token_sigs the JSON array with the token signatures. Can be NULL.
- * @param tokens where to store the parsed tokens.
- * @param num_tokens where to store the length of the @a tokens array.
- */
-static enum GNUNET_GenericReturnValue
-parse_tokens (const json_t *token_sigs,
- struct TALER_MERCHANT_OutputToken **tokens,
- unsigned int *num_tokens)
-{
- GNUNET_array_grow (*tokens,
- *num_tokens,
- json_array_size (token_sigs));
-
- for (unsigned int i = 0; i<(*num_tokens); i++)
- {
- struct TALER_MERCHANT_OutputToken *token = &(*tokens)[i];
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_blinded_token_issue_sig ("blind_sig",
- &token->blinded_sig),
- GNUNET_JSON_spec_end ()
- };
- const json_t *jtoken
- = json_array_get (token_sigs,
- i);
-
- if (NULL == jtoken)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jtoken,
- spec,
- NULL, NULL))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
-
- return GNUNET_YES;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /pay request.
- *
- * @param cls the `struct TALER_MERCHANT_Pay`
- * @param response_code HTTP response code, 0 on error
- * @param resp response body, NULL if not in JSON
- */
-static void
-handle_finished (void *cls,
- long response_code,
- const void *resp)
-{
- struct TALER_MERCHANT_OrderPayHandle *oph = cls;
- const json_t *json = resp;
- struct TALER_MERCHANT_PayResponse pr = {
- .hr.http_status = (unsigned int) response_code,
- .hr.reply = json
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Received /pay response with status code %u\n",
- (unsigned int) response_code);
-
- json_dumpf (json,
- stderr,
- JSON_INDENT (2));
-
- oph->job = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/pay completed with response code %u\n",
- (unsigned int) response_code);
- switch (response_code)
- {
- case 0:
- pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (oph->am_wallet)
- {
- const json_t *token_sigs = NULL;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("sig",
- &pr.details.ok.merchant_sig),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("pos_confirmation",
- &pr.details.ok.pos_confirmation),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("token_sigs",
- &token_sigs),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- pr.hr.http_status = 0;
- pr.hr.hint = "sig field missing in response";
- break;
- }
-
- if (GNUNET_OK !=
- parse_tokens (token_sigs,
- &pr.details.ok.tokens,
- &pr.details.ok.num_tokens))
- {
- GNUNET_break_op (0);
- pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- pr.hr.http_status = 0;
- pr.hr.hint = "failed to parse token_sigs field in response";
- break;
- }
-
- if (GNUNET_OK !=
- TALER_merchant_pay_verify (&oph->h_contract_terms,
- &oph->merchant_pub,
- &pr.details.ok.merchant_sig))
- {
- GNUNET_break_op (0);
- pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- pr.hr.http_status = 0;
- pr.hr.hint = "signature invalid";
- }
- }
- break;
- /* Tolerating Not Acceptable because sometimes
- * - especially in tests - we might want to POST
- * coins one at a time. */
- case MHD_HTTP_NOT_ACCEPTABLE:
- pr.hr.ec = TALER_JSON_get_error_code (json);
- pr.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_BAD_REQUEST:
- pr.hr.ec = TALER_JSON_get_error_code (json);
- pr.hr.hint = TALER_JSON_get_error_hint (json);
- /* This should never happen, either us
- * or the merchant is buggy (or API version conflict);
- * just pass JSON reply to the application */
- break;
- case MHD_HTTP_PAYMENT_REQUIRED:
- /* was originally paid, but then refunded */
- pr.hr.ec = TALER_JSON_get_error_code (json);
- pr.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_FORBIDDEN:
- pr.hr.ec = TALER_JSON_get_error_code (json);
- pr.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_NOT_FOUND:
- pr.hr.ec = TALER_JSON_get_error_code (json);
- pr.hr.hint = TALER_JSON_get_error_hint (json);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the
- application */
- break;
- case MHD_HTTP_REQUEST_TIMEOUT:
- pr.hr.ec = TALER_JSON_get_error_code (json);
- pr.hr.hint = TALER_JSON_get_error_hint (json);
- /* The merchant couldn't generate a timely response, likely because
- it itself waited too long on the exchange.
- Pass on to application. */
- break;
- case MHD_HTTP_CONFLICT:
- TALER_MERCHANT_parse_error_details_ (json,
- MHD_HTTP_CONFLICT,
- &pr.hr);
- break;
- case MHD_HTTP_GONE:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &pr.hr);
- /* The merchant says we are too late, the offer has expired or some
- denomination key of a coin involved has expired.
- Might be a disagreement in timestamps? Still, pass on to application. */
- break;
- case MHD_HTTP_PRECONDITION_FAILED:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &pr.hr);
- /* Nothing really to verify, the merchant is blaming us for failing to
- satisfy some constraint (likely it does not like our exchange because
- of some disagreement on the PKI). We should pass the JSON reply to the
- application */
- break;
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- {
- json_t *ebus = json_object_get (json,
- "exchange_base_urls");
- if (NULL == ebus)
- {
- GNUNET_break_op (0);
- pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- pr.hr.http_status = 0;
- pr.hr.hint = "failed to parse exchange_base_urls field in response";
- break;
- }
- {
- size_t alen = json_array_size (ebus);
- const char *ebua[GNUNET_NZL (alen)];
- size_t idx;
- json_t *jebu;
- bool ok = true;
-
- GNUNET_assert (alen <= UINT_MAX);
- json_array_foreach (ebus, idx, jebu)
- {
- ebua[idx] = json_string_value (jebu);
- if (NULL == ebua[idx])
- {
- GNUNET_break_op (0);
- pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- pr.hr.http_status = 0;
- pr.hr.hint = "non-string value in exchange_base_urls in response";
- ok = false;
- break;
- }
- }
- if (! ok)
- break;
- pr.details.unavailable_for_legal_reasons.num_exchanges
- = (unsigned int) alen;
- pr.details.unavailable_for_legal_reasons.exchanges
- = ebua;
- oph->cb (oph->cb_cls,
- &pr);
- TALER_MERCHANT_order_pay_cancel1 (oph);
- return;
- }
- }
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &pr.hr);
- /* Server had an internal issue; we should retry,
- but this API leaves this to the application */
- break;
- case MHD_HTTP_BAD_GATEWAY:
- /* Nothing really to verify, the merchant is blaming the exchange.
- We should pass the JSON reply to the application */
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &pr.hr);
- break;
- case MHD_HTTP_SERVICE_UNAVAILABLE:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &pr.hr);
- /* Exchange couldn't respond properly; the retry is
- left to the application */
- break;
- case MHD_HTTP_GATEWAY_TIMEOUT:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &pr.hr);
- /* Exchange couldn't respond in a timely fashion;
- the retry is left to the application */
- break;
- default:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &pr.hr);
- /* unexpected response code */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) pr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- oph->cb (oph->cb_cls,
- &pr);
-
- if (pr.details.ok.tokens)
- {
- GNUNET_free (pr.details.ok.tokens);
- pr.details.ok.tokens = NULL;
- pr.details.ok.num_tokens = 0;
- }
-
- TALER_MERCHANT_order_pay_cancel1 (oph);
-}
-
-
-/**
- * @brief Create and initialize a new payment handle
- *
- * Allocates a TALER_MERCHANT_OrderPayHandle, sets up its context and
- * prepares an empty JSON body for the /orders/$ID/pay request.
- */
-struct TALER_MERCHANT_OrderPayHandle *
-TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx,
- TALER_MERCHANT_OrderPayCallback cb,
- TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE
- *cb_cls)
-{
- struct TALER_MERCHANT_OrderPayHandle *ph =
- GNUNET_new (struct TALER_MERCHANT_OrderPayHandle);
- ph->ctx = ctx;
- ph->cb = cb;
- ph->cb_cls = cb_cls;
- ph->body = json_object ();
- GNUNET_assert (ph->body);
- return ph;
-}
-
-
-/**
- * @brief Cancel and free a payment handle
- *
- * Aborts any in-flight CURL job, releases all JSON objects and internal
- * buffers, and frees the handle structure itself.
- */
-void
-TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph)
-{
- if (ph->job)
- GNUNET_CURL_job_cancel (ph->job);
- ph->job = NULL;
-
- TALER_curl_easy_post_finished (&ph->post_ctx);
-
- if (ph->body)
- json_decref (ph->body);
- ph->body = NULL;
-
- if (ph->wallet_data)
- json_decref (ph->wallet_data);
- ph->wallet_data = NULL;
-
- if (ph->tokens_evs)
- json_decref (ph->tokens_evs);
- ph->tokens_evs = NULL;
-
- if (ph->donau_data)
- json_decref (ph->donau_data);
-
- GNUNET_free (ph->url);
- GNUNET_free (ph->merchant_url);
- GNUNET_free (ph->session_id);
- GNUNET_free (ph->order_id);
- GNUNET_free (ph);
-}
-
-
-/**
- * @brief Store a JSON snippet under a payment option key
- *
- * Ensures that an option of type @a ot has not already been set,
- * then merges @a snippet into the handle's JSON @c body. Marks the
- * field_seen flag and frees @a snippet.
- *
- * @param ph payment handle receiving the snippet
- * @param ot option type under which to store @a snippet
- * @param snippet JSON object representing the option payload
- * @return #TALER_MERCHANT_OPOEC_OK if stored; appropriate error code otherwise
- */
-static enum TALER_MERCHANT_OrderPayErrorCode
-store_json_option (struct TALER_MERCHANT_OrderPayHandle *ph,
- enum TALER_MERCHANT_OrderPayOptionType ot,
- json_t *snippet)
-{
- if (ph->field_seen[ot])
- {
- json_decref (snippet);
- return TALER_MERCHANT_OPOEC_DUPLICATE_OPTION;
- }
- ph->field_seen[ot] = true;
- GNUNET_assert (0 == json_object_update (ph->body,
- snippet));
- json_decref (snippet);
- return TALER_MERCHANT_OPOEC_OK;
-}
-
-
-/**
- * @brief Apply user-supplied options to a payment handle
- *
- * Iterates through a NULL-terminated array of #TALER_MERCHANT_OrderPayOption
- * entries, validates and stores each into @a ph. Handles JSON packing and
- * internal state updates for coins, tokens, deadlines, Donau data, etc.
- */
-enum TALER_MERCHANT_OrderPayErrorCode
-TALER_MERCHANT_order_pay_set_options (
- struct TALER_MERCHANT_OrderPayHandle *ph,
- const struct TALER_MERCHANT_OrderPayOption options[],
- size_t max_options)
-{
- for (size_t i = 0; i < max_options
- && options[i].ot != TALER_MERCHANT_OrderPayOptionType_END; i++)
- {
- const struct TALER_MERCHANT_OrderPayOption *o = &options[i];
-
- switch (o->ot)
- {
- case TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL:
- ph->merchant_url = GNUNET_strdup (o->details.merchant_url);
- break;
-
- case TALER_MERCHANT_OrderPayOptionType_SESSION_ID:
- ph->session_id = GNUNET_strdup (o->details.session_id);
- /* add straight into JSON body */
- {
- json_t *js = GNUNET_JSON_PACK (GNUNET_JSON_pack_string ("session_id",
- o->details.
- session_id));
- enum TALER_MERCHANT_OrderPayErrorCode ec =
- store_json_option (ph,
- o->ot,
- js);
- if (TALER_MERCHANT_OPOEC_OK != ec)
- return ec;
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_ORDER_ID:
- {
- ph->order_id = GNUNET_strdup (o->details.order_id);
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_H_CONTRACT:
- {
- ph->h_contract_terms = *o->details.h_contract;
- ph->has_h_contract = true;
-
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX:
- ph->choice_index = o->details.choice_index;
- ph->has_choice_index = true;
- break;
-
- case TALER_MERCHANT_OrderPayOptionType_AMOUNT:
- {
- ph->amount = &o->details.amount;
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_MAX_FEE:
- {
- ph->max_fee = &o->details.max_fee;
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB:
- {
- ph->merchant_pub = o->details.merchant_pub;
- ph->has_merchant_pub = true;
-
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_TIMESTAMP:
- {
- ph->timestamp = o->details.timestamp;
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE:
- {
- ph->refund_deadline = o->details.refund_deadline;
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE:
- {
- /* FIXME: This one comes from the merchant_api_post_order_pay
- no idea do we still need it or not? */
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_H_WIRE:
- {
- ph->h_wire = o->details.h_wire;
- ph->has_h_wire = true;
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_COINS:
- /* stash for later signing */
- ph->coins.num_coins = o->details.coins.num_coins;
- ph->coins.coins = o->details.coins.coins;
- break;
-
- case TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS:
- /* stash for later signing */
- ph->input_tokens.num_tokens = o->details.input_tokens.num_tokens;
- ph->input_tokens.tokens = o->details.input_tokens.tokens;
- break;
-
- case TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS:
- /* store JSON array directly, *and* stash for hash */
- ph->output_tokens.num_output_tokens =
- o->details.output_tokens.num_output_tokens;
- ph->output_tokens.output_tokens = o->details.output_tokens.output_tokens;
- {
- /* build and store tokens_evs */
- json_t *arr = json_array ();
-
- GNUNET_assert (NULL != arr);
- for (unsigned j = 0; j < ph->output_tokens.num_output_tokens; j++)
- {
- const struct TALER_MERCHANT_OutputToken *otk =
- &ph->output_tokens.output_tokens[j];
- json_t *je = GNUNET_JSON_PACK (TALER_JSON_pack_token_envelope (NULL,
- &otk->
- envelope));
- GNUNET_assert (0 ==
- json_array_append_new (arr,
- je));
- }
-
- ph->tokens_evs = arr;
-
- ph->field_seen[o->ot] = true;
- }
- break;
-
- case TALER_MERCHANT_OrderPayOptionType_DONAU_URL:
- if (NULL == ph->donau_data)
- ph->donau_data = json_object ();
- GNUNET_assert (0 ==
- json_object_set_new (
- ph->donau_data,
- "url",
- json_string (o->details.donau_url)));
- break;
-
- case TALER_MERCHANT_OrderPayOptionType_DONAU_YEAR:
- {
- if (ph->donau_data == NULL)
- ph->donau_data = json_object ();
- GNUNET_assert (0 == json_object_set_new (
- ph->donau_data,
- "year",
- json_integer ((json_int_t) o->details.donau_year)));
- break;
- }
-
- case TALER_MERCHANT_OrderPayOptionType_DONAU_BUDIS:
- {
- if (ph->donau_data == NULL)
- ph->donau_data = json_object ();
- GNUNET_assert (0 == json_object_set_new (
- ph->donau_data,
- "budikeypairs",
- json_incref (o->details.donau_budis_json)));
- break;
- }
-
- default:
- return TALER_MERCHANT_OPOEC_UNKNOWN_OPTION;
- }
- }
- return TALER_MERCHANT_OPOEC_OK;
-}
-
-
-/**
- * @brief Dispatch the /orders/$ID/pay request
- *
- * Validates that all mandatory parameters (merchant_url, order_id, coins)
- * have been set, builds the final JSON payload, constructs the URL,
- * and issues an asynchronous HTTP POST. The payment handle's callback
- * will receive completion notifications.
- */
-enum TALER_MERCHANT_OrderPayErrorCode
-TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph)
-{
- /* all the old mandatory checks */
- if ( (! ph->merchant_url) ||
- (! ph->order_id) )
- {
- GNUNET_break (0);
- return TALER_MERCHANT_OPOEC_MISSING_MANDATORY;
- }
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (ph->amount,
- ph->max_fee))
- {
- GNUNET_break (0);
- return TALER_MERCHANT_OPOEC_INVALID_VALUE;
- }
-
- /* build wallet_data hash for signing coins & tokens */
- if (ph->has_choice_index)
- {
- /* base fields */
- json_t *wd = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("choice_index",
- ph->choice_index),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_incref ("tokens_evs",
- ph->tokens_evs))
- );
-
- /* Putting prepared donau_data into the wallet_data */
- if (ph->donau_data)
- GNUNET_assert (0 == json_object_set_new (
- wd,
- "donau",
- json_incref (ph->donau_data)));
-
- ph->wallet_data = wd;
-
- TALER_json_hash (ph->wallet_data,
- &ph->wallet_data_hash);
-
- store_json_option (ph,
- TALER_MERCHANT_OrderPayOptionType_WALLET_DATA,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_object_incref ("wallet_data",
- ph->wallet_data)));
- }
-
- /* sign coins AND build the “coins” JSON in one pass */
- {
- struct TALER_Amount total_fee;
- struct TALER_Amount total_amount;
- json_t *arr = json_array ();
-
- GNUNET_assert (NULL != arr);
- for (unsigned i = 0; i < ph->coins.num_coins; i++)
- {
- const struct TALER_MERCHANT_PayCoin *c = &ph->coins.coins[i];
- struct TALER_MERCHANT_PaidCoin pc;
- json_t *je;
-
- /* sign */
- struct TALER_Amount fee;
- struct TALER_DenominationHashP h_denom_pub;
-
- TALER_denom_pub_hash (&c->denom_pub,
- &h_denom_pub);
- if (0 > TALER_amount_subtract (&fee,
- &c->amount_with_fee,
- &c->amount_without_fee))
- return TALER_MERCHANT_OPOEC_INVALID_VALUE;
-
-
- TALER_wallet_deposit_sign (&c->amount_with_fee,
- &fee,
- &ph->h_wire,
- &ph->h_contract_terms,
- (NULL != ph->wallet_data)
- ? &ph->wallet_data_hash
- : NULL,
- c->h_age_commitment,
- NULL,
- &h_denom_pub,
- ph->timestamp,
- &ph->merchant_pub,
- ph->refund_deadline,
- &c->coin_priv,
- &pc.coin_sig);
-
- pc.denom_pub = c->denom_pub;
- pc.denom_sig = c->denom_sig;
- pc.denom_value = c->denom_value;
- pc.amount_with_fee = c->amount_with_fee;
- pc.amount_without_fee = c->amount_without_fee;
- pc.exchange_url = c->exchange_url;
- GNUNET_CRYPTO_eddsa_key_get_public (&c->coin_priv.eddsa_priv,
- &pc.coin_pub.eddsa_pub);
-
- /* JSON */
- je = GNUNET_JSON_PACK (TALER_JSON_pack_amount ("contribution",
- &pc.amount_with_fee),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &pc.coin_pub),
- GNUNET_JSON_pack_string ("exchange_url",
- pc.exchange_url),
- GNUNET_JSON_pack_data_auto ("h_denom",
- &h_denom_pub),
- TALER_JSON_pack_denom_sig ("ub_sig",
- &pc.denom_sig),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &pc.coin_sig));
- GNUNET_assert (0 ==
- json_array_append_new (arr,
- je));
-
- /* optional totals if you need them later
- (kept here because they existed in the legacy code) */
- if (0 == i)
- {
- total_fee = fee;
- total_amount = pc.amount_with_fee;
- }
- else
- {
- if ( (0 >
- TALER_amount_add (&total_fee,
- &total_fee,
- &fee)) ||
- (0 >
- TALER_amount_add (&total_amount,
- &total_amount,
- &pc.amount_with_fee)) )
- {
- return TALER_MERCHANT_OPOEC_INVALID_VALUE;
- }
- }
- }
-
- /* Putting coins to the body*/
- {
- enum TALER_MERCHANT_OrderPayErrorCode ec =
- store_json_option (ph,
- TALER_MERCHANT_OrderPayOptionType_COINS,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("coins",
- arr)
- ));
- if (TALER_MERCHANT_OPOEC_OK != ec)
- {
- return ec;
- }
- }
- }
-
- /* sign & pack input_tokens into used_tokens array in body */
- if (ph->input_tokens.num_tokens > 0)
- {
- struct TALER_MERCHANT_UsedToken ut[ph->input_tokens.num_tokens];
- json_t *arr = json_array ();
-
- GNUNET_assert (NULL != arr);
- for (unsigned i = 0; i < ph->input_tokens.num_tokens; i++)
- {
- json_t *je;
- const struct TALER_MERCHANT_UseToken *in = &ph->input_tokens.tokens[i];
- struct TALER_MERCHANT_UsedToken *t = &ut[i];
-
- TALER_wallet_token_use_sign (&ph->h_contract_terms,
- &ph->wallet_data_hash,
- &in->token_priv,
- &t->token_sig);
-
- t->ub_sig = in->ub_sig;
- t->issue_pub = in->issue_pub;
-
- GNUNET_CRYPTO_eddsa_key_get_public (&in->token_priv.private_key,
- &t->token_pub.public_key);
-
- je = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("token_sig",
- &t->token_sig),
- TALER_JSON_pack_token_issue_sig ("ub_sig",
- &t->ub_sig),
- GNUNET_JSON_pack_data_auto ("h_issue",
- &t->issue_pub.public_key->pub_key_hash),
- GNUNET_JSON_pack_data_auto ("token_pub",
- &t->token_pub)
- );
- GNUNET_assert (0 ==
- json_array_append_new (arr,
- je));
- }
-
- store_json_option (ph,
- TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("tokens",
- arr)
- )
- );
- }
-
-
- /* post the request */
- {
- char *path;
- CURL *eh;
- GNUNET_asprintf (&path,
- "orders/%s/pay",
- ph->order_id);
- ph->url = TALER_url_join (ph->merchant_url,
- path,
- NULL);
- GNUNET_free (path);
-
- if (NULL == ph->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- json_decref (ph->body);
- GNUNET_free (ph);
- return TALER_MERCHANT_OPOEC_URL_FAILURE;
- }
-
- eh = TALER_MERCHANT_curl_easy_get_ (ph->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&ph->post_ctx,
- eh,
- ph->body))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- GNUNET_free (ph->url);
- GNUNET_free (ph);
- return TALER_MERCHANT_OPOEC_CURL_FAILURE;
- }
-
- ph->job = GNUNET_CURL_job_add2 (ph->ctx,
- eh,
- ph->post_ctx.headers,
- &handle_finished,
- ph);
-
- ph->am_wallet = true;
- return TALER_MERCHANT_OPOEC_OK;
- }
-}