summaryrefslogtreecommitdiff
path: root/src/lib/merchant_api_post_order_pay.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/merchant_api_post_order_pay.c')
-rw-r--r--src/lib/merchant_api_post_order_pay.c285
1 files changed, 96 insertions, 189 deletions
diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c
index c246a1d4..57c85565 100644
--- a/src/lib/merchant_api_post_order_pay.c
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
@@ -30,6 +30,7 @@
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_merchant_service.h"
+#include "merchant_api_common.h"
#include "merchant_api_curl_defaults.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>
@@ -91,6 +92,23 @@ struct TALER_MERCHANT_OrderPayHandle
struct TALER_MerchantPublicKeyP merchant_pub;
/**
+ * JSON with the full reply, used during async
+ * processing.
+ */
+ json_t *full_reply;
+
+ /**
+ * Pointer into @e coins array for the coin that
+ * created a conflict (that we are checking).
+ */
+ const struct TALER_MERCHANT_PaidCoin *error_pc;
+
+ /**
+ * Coin history that proves a conflict.
+ */
+ json_t *error_history;
+
+ /**
* Number of @e coins we are paying with.
*/
unsigned int num_coins;
@@ -105,132 +123,6 @@ struct TALER_MERCHANT_OrderPayHandle
/**
- * We got a 409 response back from the exchange (or the merchant).
- * Now we need to check the provided cryptograophic proof that the
- * coin was actually already spent!
- *
- * @param pc handle of the original coin we paid with
- * @param json cryptograophic proof of coin's transaction
- * history as was returned by the exchange/merchant
- * @return #GNUNET_OK if proof checks out
- */
-static int
-check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc,
- json_t *json)
-{
- struct TALER_Amount spent;
- struct TALER_Amount spent_plus_contrib;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_DenominationHashP h_denom_pub_pc;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */
- pc->amount_with_fee.currency,
- &pc->coin_pub,
- json,
- &h_denom_pub,
- &spent))
- {
- /* Exchange's history fails to verify */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&spent_plus_contrib,
- &spent,
- &pc->amount_with_fee))
- {
- /* We got an integer overflow? Bad application! */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- TALER_denom_pub_hash (&pc->denom_pub,
- &h_denom_pub_pc);
- if ( (-1 != TALER_amount_cmp (&pc->denom_value,
- &spent_plus_contrib)) &&
- (0 != GNUNET_memcmp (&h_denom_pub,
- &h_denom_pub_pc)) )
- {
- /* according to our calculations, the transaction should
- have still worked, AND we did not get any proof of
- coin public key re-use; hence: exchange error! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Accepting proof of double-spending (or coin public key re-use)\n");
- return GNUNET_OK;
-}
-
-
-/**
- * We got a 409 response back from the exchange (or the merchant).
- * Now we need to check the provided cryptograophic proof that the
- * coin was actually already spent!
- *
- * @param oph handle of the original pay operation
- * @param json cryptograophic proof returned by the
- * exchange/merchant
- * @return #GNUNET_OK if proof checks out
- */
-static enum GNUNET_GenericReturnValue
-check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
- const json_t *json)
-{
- json_t *history;
- json_t *ereply;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("exchange_reply", &ereply),
- GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_JSON_Specification hspec[] = {
- GNUNET_JSON_spec_json ("history", &history),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (ereply,
- hspec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (spec);
-
- for (unsigned int i = 0; i<oph->num_coins; i++)
- {
- if (0 == memcmp (&oph->coins[i].coin_pub,
- &coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP)))
- {
- int ret;
-
- ret = check_coin_history (&oph->coins[i],
- history);
- GNUNET_JSON_parse_free (hspec);
- return ret;
- }
- }
- GNUNET_break_op (0); /* complaint is not about any of the coins
- that we actually paid with... */
- GNUNET_JSON_parse_free (hspec);
- return GNUNET_SYSERR;
-}
-
-
-/**
* Function called when we're done processing the
* HTTP /pay request.
*
@@ -245,12 +137,10 @@ handle_pay_finished (void *cls,
{
struct TALER_MERCHANT_OrderPayHandle *oph = cls;
const json_t *json = response;
- struct TALER_MERCHANT_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_MERCHANT_PayResponse pr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
- struct TALER_MerchantSignatureP *merchant_sigp = NULL;
- struct TALER_MerchantSignatureP merchant_sig;
oph->job = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -259,15 +149,21 @@ handle_pay_finished (void *cls,
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (oph->am_wallet)
{
/* Here we can (and should) verify the merchant's signature */
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("sig",
- &merchant_sig),
+ 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_end ()
};
@@ -277,94 +173,90 @@ handle_pay_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.http_status = 0;
- hr.hint = "sig field missing in response";
+ 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 !=
TALER_merchant_pay_verify (&oph->h_contract_terms,
&oph->merchant_pub,
- &merchant_sig))
+ &pr.details.ok.merchant_sig))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.http_status = 0;
- hr.hint = "signature invalid";
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "signature invalid";
}
- merchant_sigp = &merchant_sig;
}
break;
/* Tolerating Not Acceptable because sometimes
* - especially in tests - we might want to POST
* coins one at a time. */
case MHD_HTTP_NOT_ACCEPTABLE:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_BAD_REQUEST:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we tried to abort the payment
* after it was successful. We should pass the JSON reply to the
* application */
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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_PRECONDITION_FAILED:
- TALER_MERCHANT_parse_error_details_ (json,
- response_code,
- &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_REQUEST_TIMEOUT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- if (GNUNET_OK != check_conflict (oph,
- json))
- {
- GNUNET_break_op (0);
- response_code = 0;
- }
+ TALER_MERCHANT_parse_error_details_ (json,
+ MHD_HTTP_CONFLICT,
+ &pr.hr);
break;
case MHD_HTTP_GONE:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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_INTERNAL_SERVER_ERROR:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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;
@@ -373,37 +265,36 @@ handle_pay_finished (void *cls,
We should pass the JSON reply to the application */
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &pr.hr);
break;
case MHD_HTTP_SERVICE_UNAVAILABLE:
TALER_MERCHANT_parse_error_details_ (json,
response_code,
- &hr);
+ &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,
- &hr);
+ &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,
- &hr);
+ &pr.hr);
/* unexpected response code */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) pr.hr.ec);
GNUNET_break_op (0);
break;
}
oph->pay_cb (oph->pay_cb_cls,
- &hr,
- merchant_sigp);
+ &pr);
TALER_MERCHANT_order_pay_cancel (oph);
}
@@ -414,8 +305,9 @@ TALER_MERCHANT_order_pay_frontend (
const char *merchant_url,
const char *order_id,
const char *session_id,
+ const json_t *wallet_data,
unsigned int num_coins,
- const struct TALER_MERCHANT_PaidCoin coins[],
+ const struct TALER_MERCHANT_PaidCoin coins[static num_coins],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls)
{
@@ -432,6 +324,7 @@ TALER_MERCHANT_order_pay_frontend (
return NULL;
}
j_coins = json_array ();
+ GNUNET_assert (NULL != j_coins);
for (unsigned int i = 0; i<num_coins; i++)
{
json_t *j_coin;
@@ -503,6 +396,9 @@ TALER_MERCHANT_order_pay_frontend (
GNUNET_JSON_pack_array_steal ("coins",
j_coins),
GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("wallet_data",
+ (json_t *) wallet_data)),
+ GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("session_id",
session_id)));
@@ -532,9 +428,9 @@ TALER_MERCHANT_order_pay_frontend (
oph->num_coins = num_coins;
oph->coins = GNUNET_new_array (num_coins,
struct TALER_MERCHANT_PaidCoin);
- memcpy (oph->coins,
- coins,
- num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
+ GNUNET_memcpy (oph->coins,
+ coins,
+ num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
if (GNUNET_OK !=
@@ -565,6 +461,7 @@ TALER_MERCHANT_order_pay (
const char *merchant_url,
const char *session_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
+ const json_t *wallet_data,
const struct TALER_Amount *amount,
const struct TALER_Amount *max_fee,
const struct TALER_MerchantPublicKeyP *merchant_pub,
@@ -575,10 +472,12 @@ TALER_MERCHANT_order_pay (
const struct TALER_MerchantWireHashP *h_wire,
const char *order_id,
unsigned int num_coins,
- const struct TALER_MERCHANT_PayCoin coins[],
+ const struct TALER_MERCHANT_PayCoin coins[static num_coins],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls)
{
+ struct GNUNET_HashCode wallet_data_hash;
+
if (GNUNET_YES !=
TALER_amount_cmp_currency (amount,
max_fee))
@@ -586,7 +485,9 @@ TALER_MERCHANT_order_pay (
GNUNET_break (0);
return NULL;
}
-
+ if (NULL != wallet_data)
+ TALER_json_hash (wallet_data,
+ &wallet_data_hash);
{
struct TALER_MERCHANT_PaidCoin pc[num_coins];
@@ -613,6 +514,9 @@ TALER_MERCHANT_order_pay (
&fee,
h_wire,
h_contract_terms,
+ (NULL != wallet_data)
+ ? &wallet_data_hash
+ : NULL,
coin->h_age_commitment,
NULL /* h_extensions! */,
&h_denom_pub,
@@ -637,6 +541,7 @@ TALER_MERCHANT_order_pay (
merchant_url,
order_id,
session_id,
+ wallet_data,
num_coins,
pc,
pay_cb,
@@ -661,6 +566,8 @@ TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph)
oph->job = NULL;
}
TALER_curl_easy_post_finished (&oph->post_ctx);
+ json_decref (oph->error_history);
+ json_decref (oph->full_reply);
GNUNET_free (oph->coins);
GNUNET_free (oph->url);
GNUNET_free (oph);