commit 6be0a920e35cea0dbbaf35fbc650321a3a87f6c1
parent d1f0265a59c858eb03157fb83daf0a5bfebaf004
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Wed, 23 Apr 2025 19:12:13 +0200
first part done, to be tested
Diffstat:
1 file changed, 283 insertions(+), 17 deletions(-)
diff --git a/src/lib/taler_merchant_pay_service.c b/src/lib/taler_merchant_pay_service.c
@@ -54,6 +54,9 @@ struct TALER_MERCHANT_OrderPayHandle
struct GNUNET_TIME_Timestamp timestamp;
struct GNUNET_TIME_Timestamp refund_deadline;
+ struct TALER_MerchantWireHashP h_wire;
+ bool has_h_wire;
+
/* for wallet mode: */
bool has_h_contract;
struct TALER_PrivateContractHashP h_contract_terms;
@@ -93,6 +96,13 @@ struct TALER_MERCHANT_OrderPayHandle
struct GNUNET_CURL_Job *job;
bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH];
+
+ /**
+ * Function to call with the result in "pay" @e mode.
+ */
+ TALER_MERCHANT_OrderPayCallback pay_cb;
+
+ bool am_wallet;
};
/* create / destroy */
@@ -113,7 +123,7 @@ TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx,
}
void
-TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *ph)
+TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph)
{
if (ph->job)
GNUNET_CURL_job_cancel (ph->job);
@@ -248,6 +258,9 @@ TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph,
case TALER_MERCHANT_OrderPayOptionType_H_WIRE:
{
+ ph->h_wire = o->details.h_wire;
+ ph->has_h_wire = true;
+
json_t *js = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("h_wire",
&o->details.h_wire)
@@ -302,24 +315,276 @@ TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph,
/* ============= network submission ============= */
+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;
+}
+
static void
-handle_finished (void *cls, long hc, const void *resp)
+handle_finished (void *cls,
+ long response_code,
+ const void *resp)
{
- struct TALER_MERCHANT_OrderPayHandle *ph = cls;
+ struct TALER_MERCHANT_OrderPayHandle *oph = cls;
+ const json_t *json = resp;
struct TALER_MERCHANT_PayResponse pr = {
- .hr.http_status = (unsigned int) hc,
- .hr.reply = resp ? (const json_t *)resp : NULL,
- .hr.ec = (resp && json_is_object (resp))
- ? TALER_JSON_get_error_code ((const json_t *)resp)
- : TALER_EC_NONE,
- .hr.hint = (resp && json_is_object (resp))
- ? TALER_JSON_get_error_hint ((const json_t *)resp)
- : NULL
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
- ph->job = NULL;
- ph->cb (ph->cb_cls, &pr);
- TALER_MERCHANT_order_pay_cancel (ph);
+ 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->pay_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->pay_cb (oph->cb_cls,
+ &pr);
+ TALER_MERCHANT_order_pay_cancel1 (oph);
}
static enum TALER_MERCHANT_OrderPayOptionErrorCode
@@ -372,6 +637,7 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph)
const struct TALER_MERCHANT_PayCoin *coin = &ph->coins.coins[i];
struct TALER_MERCHANT_PaidCoin *p = &pc[i];
struct TALER_Amount fee;
+ struct TALER_DenominationHashP h_denom_pub;
if (0 >
TALER_amount_subtract (&fee,
@@ -385,7 +651,7 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph)
}
TALER_denom_pub_hash ( &coin->denom_pub,
- &p->denom_pub);
+ &h_denom_pub);
TALER_wallet_deposit_sign ( &coin->amount_with_fee,
&fee,
&ph->h_wire,
@@ -395,10 +661,10 @@ TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph)
: NULL,
coin->h_age_commitment,
NULL, /* extensions */
- &p->denom_pub,
+ &h_denom_pub,
ph->timestamp,
- ph->refund_deadline,
&ph->merchant_pub,
+ ph->refund_deadline,
&coin->coin_priv,
&p->coin_sig);