merchant

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

commit 3041f1111e5878b02c7ec48999966fd7c1a047d3
parent 93465baaf1a765b3fe66a61a0f6d0de551757abd
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 25 Mar 2026 23:07:33 +0100

more API cleanups

Diffstat:
Msrc/include/taler/taler-merchant/common.h | 13+++++++++++++
Msrc/include/taler/taler-merchant/get-orders-ORDER_ID.h | 14++++++++++++++
Msrc/include/taler/taler-merchant/get-private-orders-ORDER_ID.h | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/include/taler/taler-merchant/get-private-orders.h | 30+++++++++++++++++++++++++++++-
Msrc/include/taler/taler-merchant/post-private-orders.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/merchant_api_get-orders-ORDER_ID.c | 43++++++++++++++++++++++++++++++++++++++++++-
Msrc/lib/merchant_api_get-private-kyc.c | 15+++++++++++++++
Msrc/lib/merchant_api_get-private-orders-ORDER_ID.c | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/lib/merchant_api_get-private-orders.c | 26++++++++++++++++++++++++++
Msrc/lib/merchant_api_post-orders-ORDER_ID-pay.c | 5+++++
Msrc/lib/merchant_api_post-private-orders.c | 101++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Dsrc/lib/taler_merchant_pay_service.c | 1070-------------------------------------------------------------------------------
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; - } -}