summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-04-29 22:02:54 +0200
committerChristian Grothoff <christian@grothoff.org>2020-04-29 22:02:54 +0200
commitce97f3d2e12b4da31eb0f611d401f56ce8052de5 (patch)
treed3afb87b14d8ced353fdffc655f38623ce71a7ee
parent5da121e9b0ec83f20a1a404f7049f9ff19aca32b (diff)
downloadmerchant-ce97f3d2e12b4da31eb0f611d401f56ce8052de5.tar.gz
merchant-ce97f3d2e12b4da31eb0f611d401f56ce8052de5.tar.bz2
merchant-ce97f3d2e12b4da31eb0f611d401f56ce8052de5.zip
work on /pay API revision
-rw-r--r--src/backend/Makefile.am4
-rw-r--r--src/backend/taler-merchant-httpd.c4
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c1888
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.h27
-rw-r--r--src/include/taler_merchant_service.h504
-rw-r--r--src/include/taler_merchantdb_plugin.h189
6 files changed, 1133 insertions, 1483 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 316f6e30..4f74af43 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -52,7 +52,9 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-post-orders.c \
taler-merchant-httpd_private-post-orders.h \
taler-merchant-httpd_post-orders-ID-claim.c \
- taler-merchant-httpd_post-orders-ID-claim.h
+ taler-merchant-httpd_post-orders-ID-claim.h \
+ taler-merchant-httpd_post-orders-ID-pay.c \
+ taler-merchant-httpd_post-orders-ID-pay.h
DEAD = \
taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 7d255b9e..0149b8b0 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -513,6 +513,8 @@ handle_mhd_completion_callback (void *cls,
GNUNET_free_non_null (hc->infix);
if (NULL != hc->request_body)
json_decref (hc->request_body);
+ if (NULL != hc->instance)
+ TMH_instance_decref (hc->instance);
GNUNET_free (hc);
*con_cls = NULL;
}
@@ -972,6 +974,8 @@ url_handler (void *cls,
/* use 'default' */
hc->instance = TMH_lookup_instance (NULL);
}
+ if (NULL != hc->instance)
+ hc->instance->rc++;
}
{
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 7a1b7fd8..004b5e40 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -18,22 +18,19 @@
*/
/**
- * @file backend/taler-merchant-httpd_pay.c
- * @brief handling of /pay requests
+ * @file backend/taler-merchant-httpd_post-orders-ID-pay.c
+ * @brief handling of POST /orders/$ID/pay requests
* @author Marcello Stanisci
* @author Christian Grothoff
* @author Florian Dold
*/
#include "platform.h"
-#include <jansson.h>
-#include <gnunet/gnunet_util_lib.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_refund.h"
+#include "taler-merchant-httpd_post-orders-ID-pay.h"
/**
@@ -48,12 +45,12 @@
#define MAX_RETRIES 5
/**
- * Information we keep for an individual call to the /pay handler.
+ * Information we keep for an individual call to the pay handler.
*/
struct PayContext;
/**
- * Information kept during a /pay request for each coin.
+ * Information kept during a pay request for each coin.
*/
struct DepositConfirmation
{
@@ -75,9 +72,9 @@ struct DepositConfirmation
char *exchange_url;
/**
- * Denomination of this coin.
+ * Hash of the denomination of this coin.
*/
- struct TALER_DenominationPublicKey denom;
+ struct GNUNET_HashCode h_denom;
/**
* Amount this coin contributes to the total purchase price.
@@ -122,14 +119,9 @@ struct DepositConfirmation
unsigned int index;
/**
- * #GNUNET_YES if we found this coin in the database.
- */
- int found_in_db;
-
- /**
- * #GNUNET_YES if this coin was refunded.
+ * true if we found this coin in the database.
*/
- int refunded;
+ bool found_in_db;
};
@@ -141,12 +133,6 @@ struct PayContext
{
/**
- * This field MUST be first for handle_mhd_completion_callback() to work
- * when it treats this struct as a `struct TM_HandlerContext`.
- */
- struct TM_HandlerContext hc;
-
- /**
* Stored in a DLL.
*/
struct PayContext *next;
@@ -167,21 +153,15 @@ struct PayContext
struct MHD_Connection *connection;
/**
- * Instance of the payment's instance (in JSON format)
+ * Details about the client's request.
*/
- struct MerchantInstance *mi;
+ struct TMH_HandlerContext *hc;
/**
* What wire method (of the @e mi) was selected by the wallet?
* Set in #parse_pay().
*/
- struct WireMethod *wm;
-
- /**
- * Proposal data for the proposal that is being
- * paid for in this context.
- */
- json_t *contract_terms;
+ struct TMH_WireMethod *wm;
/**
* Task called when the (suspended) processing for
@@ -196,12 +176,6 @@ struct PayContext
struct MHD_Response *response;
/**
- * Handle to the exchange that we are doing the payment with.
- * (initially NULL while @e fo is trying to find a exchange).
- */
- struct TALER_EXCHANGE_Handle *mh;
-
- /**
* Handle for operation to lookup /keys (and auditors) from
* the exchange used for this transaction; NULL if no operation is
* pending.
@@ -230,11 +204,6 @@ struct PayContext
char *order_id;
/**
- * Fulfillment URL from @e contract_terms.
- */
- char *fulfillment_url;
-
- /**
* Hashed proposal.
*/
struct GNUNET_HashCode h_contract_terms;
@@ -375,11 +344,6 @@ struct PayContext
*/
int tried_force_keys;
- /**
- * Which operational mode is the /pay request made in?
- */
- enum { PC_MODE_PAY, PC_MODE_ABORT_REFUND } mode;
-
};
@@ -496,108 +460,25 @@ resume_pay_with_error (struct PayContext *pc,
/**
- * Generate a response that indicates payment success.
- *
- * @param pc payment context
- */
-static void
-generate_success_response (struct PayContext *pc)
-{
- json_t *refunds;
- struct GNUNET_CRYPTO_EddsaSignature sig;
-
- /* Check for applicable refunds */
- {
- enum TALER_ErrorCode ec;
- const char *errmsg;
-
- refunds = TM_get_refund_json (pc->mi,
- &pc->h_contract_terms,
- &ec,
- &errmsg);
- /* We would get an EMPTY array back on success if there
- are no refunds, but not NULL. So NULL is always an error. */
- if (NULL == refunds)
- {
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- ec,
- errmsg);
- return;
- }
- }
-
- /* Sign on our end (as the payment did go through, even if it may
- have been refunded already) */
- {
- struct PaymentResponsePS mr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
- .purpose.size = htonl (sizeof (mr)),
- .h_contract_terms = pc->h_contract_terms
- };
-
- GNUNET_CRYPTO_eddsa_sign (&pc->mi->privkey.eddsa_priv,
- &mr,
- &sig);
- }
-
- /* Build the response */
- {
- json_t *resp;
-
- resp = json_pack ("{s:O, s:o, s:o, s:o}",
- "contract_terms",
- pc->contract_terms,
- "sig",
- GNUNET_JSON_from_data_auto (&sig),
- "h_contract_terms",
- GNUNET_JSON_from_data (&pc->h_contract_terms,
- sizeof (struct GNUNET_HashCode)),
- "refund_permissions",
- refunds);
- if (NULL == resp)
- {
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "could not build final response");
- return;
- }
- resume_pay_with_response (pc,
- MHD_HTTP_OK,
- TALER_MHD_make_json (resp));
- json_decref (resp);
- }
-}
-
-
-/**
* Custom cleanup routine for a `struct PayContext`.
*
- * @param hc the `struct PayContext` to clean up.
+ * @param cls the `struct PayContext` to clean up.
*/
static void
-pay_context_cleanup (struct TM_HandlerContext *hc)
+pay_context_cleanup (void *cls)
{
- struct PayContext *pc = (struct PayContext *) hc;
+ struct PayContext *pc = cls;
if (NULL != pc->timeout_task)
{
GNUNET_SCHEDULER_cancel (pc->timeout_task);
pc->timeout_task = NULL;
}
- TALER_MHD_parse_post_cleanup_callback (pc->json_parse_context);
abort_deposit (pc);
for (unsigned int i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
- if (NULL != dc->denom.rsa_public_key)
- {
- GNUNET_CRYPTO_rsa_public_key_free (dc->denom.rsa_public_key);
- dc->denom.rsa_public_key = NULL;
- }
if (NULL != dc->ub_sig.rsa_signature)
{
GNUNET_CRYPTO_rsa_signature_free (dc->ub_sig.rsa_signature);
@@ -616,14 +497,8 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
MHD_destroy_response (pc->response);
pc->response = NULL;
}
- if (NULL != pc->contract_terms)
- {
- json_decref (pc->contract_terms);
- pc->contract_terms = NULL;
- }
GNUNET_free_non_null (pc->order_id);
GNUNET_free_non_null (pc->session_id);
- GNUNET_free_non_null (pc->fulfillment_url);
GNUNET_CONTAINER_DLL_remove (pc_head,
pc_tail,
pc);
@@ -632,275 +507,6 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
/**
- * Check whether the amount paid is sufficient to cover
- * the contract.
- *
- * @param pc payment context to check
- * @return #GNUNET_OK if the payment is sufficient, #GNUNET_SYSERR if it is
- * insufficient
- */
-static int
-check_payment_sufficient (struct PayContext *pc)
-{
- struct TALER_Amount acc_fee;
- struct TALER_Amount acc_amount;
- struct TALER_Amount final_amount;
- struct TALER_Amount wire_fee_delta;
- struct TALER_Amount wire_fee_customer_contribution;
- struct TALER_Amount total_wire_fee;
- struct TALER_Amount total_needed;
-
- if (0 == pc->coins_cnt)
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PAY_PAYMENT_INSUFFICIENT,
- "insufficient funds (no coins!)");
- return GNUNET_SYSERR;
- }
-
- acc_fee = pc->dc[0].deposit_fee;
- total_wire_fee = pc->dc[0].wire_fee;
- acc_amount = pc->dc[0].amount_with_fee;
-
- /**
- * This loops calculates what are the deposit fee / total
- * amount with fee / and wire fee, for all the coins.
- */
- for (unsigned int i = 1; i<pc->coins_cnt; i++)
- {
- struct DepositConfirmation *dc = &pc->dc[i];
-
- GNUNET_assert (GNUNET_YES == dc->found_in_db);
- if ( (0 >
- TALER_amount_add (&acc_fee,
- &dc->deposit_fee,
- &acc_fee)) ||
- (0 >
- TALER_amount_add (&acc_amount,
- &dc->amount_with_fee,
- &acc_amount)) )
- {
- GNUNET_break (0);
- /* Overflow in these amounts? Very strange. */
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
- }
- if (1 ==
- TALER_amount_cmp (&dc->deposit_fee,
- &dc->amount_with_fee))
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PAY_FEES_EXCEED_PAYMENT,
- "Deposit fees exceed coin's contribution");
- return GNUNET_SYSERR;
- }
-
- /* If exchange differs, add wire fee */
- {
- int new_exchange = GNUNET_YES;
-
- for (unsigned int j = 0; j<i; j++)
- if (0 == strcasecmp (dc->exchange_url,
- pc->dc[j].exchange_url))
- {
- new_exchange = GNUNET_NO;
- break;
- }
- if (GNUNET_YES == new_exchange)
- {
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (&total_wire_fee,
- &dc->wire_fee))
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_PRECONDITION_FAILED,
- TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
- "exchange wire in different currency");
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&total_wire_fee,
- &total_wire_fee,
- &dc->wire_fee))
- {
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
- "could not add exchange wire fee to total");
- return GNUNET_SYSERR;
- }
- }
- }
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Amount received from wallet: %s\n",
- TALER_amount2s (&acc_amount));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Deposit fee for all coins: %s\n",
- TALER_amount2s (&acc_fee));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Total wire fee: %s\n",
- TALER_amount2s (&total_wire_fee));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Max wire fee: %s\n",
- TALER_amount2s (&pc->max_wire_fee));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Deposit fee limit for merchant: %s\n",
- TALER_amount2s (&pc->max_fee));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Total refunded amount: %s\n",
- TALER_amount2s (&pc->total_refunded));
-
- /* Now compare exchange wire fee compared to
- * what we are willing to pay */
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&total_wire_fee,
- &pc->max_wire_fee))
- {
- resume_pay_with_error (pc,
- MHD_HTTP_PRECONDITION_FAILED,
- TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
- "exchange wire does not match our currency");
- return GNUNET_SYSERR;
- }
-
- switch (TALER_amount_subtract (&wire_fee_delta,
- &total_wire_fee,
- &pc->max_wire_fee))
- {
- case TALER_AAR_RESULT_POSITIVE:
- /* Actual wire fee is indeed higher than our maximum,
- compute how much the customer is expected to cover! */
- TALER_amount_divide (&wire_fee_customer_contribution,
- &wire_fee_delta,
- pc->wire_fee_amortization);
- break;
- case TALER_AAR_RESULT_ZERO:
- case TALER_AAR_INVALID_NEGATIVE_RESULT:
- /* Wire fee threshold is still above the wire fee amount.
- Customer is not going to contribute on this. */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (total_wire_fee.currency,
- &wire_fee_customer_contribution));
- break;
- default:
- GNUNET_assert (0);
- }
-
- /* add wire fee contribution to the total fees */
- if (0 >
- TALER_amount_add (&acc_fee,
- &acc_fee,
- &wire_fee_customer_contribution))
- {
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
- return GNUNET_SYSERR;
- }
- if (-1 == TALER_amount_cmp (&pc->max_fee,
- &acc_fee))
- {
- /**
- * Sum of fees of *all* the different exchanges of all the coins are
- * higher than the fixed limit that the merchant is willing to pay. The
- * difference must be paid by the customer.
- *///
- struct TALER_Amount excess_fee;
-
- /* compute fee amount to be covered by customer */
- GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
- TALER_amount_subtract (&excess_fee,
- &acc_fee,
- &pc->max_fee));
- /* add that to the total */
- if (0 >
- TALER_amount_add (&total_needed,
- &excess_fee,
- &pc->amount))
- {
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
- return GNUNET_SYSERR;
- }
- }
- else
- {
- /* Fees are fully covered by the merchant, all we require
- is that the total payment is not below the contract's amount */
- total_needed = pc->amount;
- }
-
- /* Do not count refunds towards the payment */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Subtracting total refunds from paid amount: %s\n",
- TALER_amount2s (&pc->total_refunded));
- if (0 >
- TALER_amount_subtract (&final_amount,
- &acc_amount,
- &pc->total_refunded))
- {
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_REFUNDS_EXCEED_PAYMENTS,
- "refunded amount exceeds total payments");
- return GNUNET_SYSERR;
- }
-
- if (-1 == TALER_amount_cmp (&final_amount,
- &total_needed))
- {
- /* acc_amount < total_needed */
- if (-1 < TALER_amount_cmp (&acc_amount,
- &total_needed))
- {
- resume_pay_with_error (pc,
- MHD_HTTP_PAYMENT_REQUIRED,
- TALER_EC_PAY_REFUNDED,
- "contract not paid up due to refunds");
- }
- else if (-1 < TALER_amount_cmp (&acc_amount,
- &pc->amount))
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
- "contract not paid up due to fees (client may have calculated them badly)");
- }
- else
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_PAY_PAYMENT_INSUFFICIENT,
- "payment insufficient");
-
- }
- return GNUNET_SYSERR;
- }
-
-
- return GNUNET_OK;
-}
-
-
-/**
* Find the exchange we need to talk to for the next
* pending deposit permission.
*
@@ -931,13 +537,13 @@ begin_transaction (struct PayContext *pc);
* for the wallet
* @param hr HTTP response code details
* @param exchange_sig signature from the exchange over the deposit confirmation
- * @param sign_key which key did the exchange use to sign the @a proof
+ * @param exchange_pub_key which key did the exchange use to create the @a exchange_sig
*/
static void
deposit_cb (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr,
const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *sign_key)
+ const struct TALER_ExchangePublicKeyP *exchange_pub)
{
struct DepositConfirmation *dc = cls;
struct PayContext *pc = dc->pc;
@@ -1028,25 +634,27 @@ deposit_cb (void *cls,
}
return;
}
+
/* store result to DB */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing successful payment for h_contract_terms `%s' and merchant `%s'\n",
+ "Storing successful payment %s (%s) at instance `%s'\n",
+ pc->hc->infix,
GNUNET_h2s (&pc->h_contract_terms),
- TALER_B2S (&pc->mi->pubkey));
+ pc->hc->instance->settings.id);
/* NOTE: not run in any transaction block, simply as a
transaction by itself! */
- db->preflight (db->cls);
- qs = db->store_deposit (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey,
- &dc->coin_pub,
- dc->exchange_url,
- &dc->amount_with_fee,
- &dc->deposit_fee,
- &dc->refund_fee,
- &dc->wire_fee,
- sign_key,
- hr->reply);
+ TMH_db->preflight (TMH_db->cls);
+ qs = TMH_db->insert_deposit (TMH_db->cls,
+ pc->hc->instance->settings.id,
+ &pc->h_contract_terms,
+ &dc->coin_pub,
+ dc->exchange_url,
+ &dc->amount_with_fee,
+ &dc->deposit_fee,
+ &dc->refund_fee,
+ &dc->wire_fee,
+ exchange_sig,
+ exchange_pub);
if (0 > qs)
{
/* Special report if retries insufficient */
@@ -1065,7 +673,7 @@ deposit_cb (void *cls,
"Merchant database error");
return;
}
- dc->found_in_db = GNUNET_YES;
+ dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */
pc->pending--;
if (0 != pc->pending_at_ce)
@@ -1079,8 +687,8 @@ deposit_cb (void *cls,
*
* @param cls the `struct PayContext`
* @param hr HTTP response details
- * @param mh NULL if exchange was not found to be acceptable
- * @param wire_fee current applicable fee for dealing with @a mh,
+ * @param exchange_handle NULL if exchange was not found to be acceptable
+ * @param wire_fee current applicable fee for dealing with @a exchange_handle,
* NULL if not available
* @param exchange_trusted #GNUNET_YES if this exchange is
* trusted by config
@@ -1088,11 +696,12 @@ deposit_cb (void *cls,
static void
process_pay_with_exchange (void *cls,
const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *mh,
+ struct TALER_EXCHANGE_Handle *exchange_handle,
const struct TALER_Amount *wire_fee,
int exchange_trusted)
{
struct PayContext *pc = cls;
+ struct TMH_HandlerContext *hc = pc->hc;
const struct TALER_EXCHANGE_Keys *keys;
pc->fo = NULL;
@@ -1120,8 +729,7 @@ process_pay_with_exchange (void *cls,
hr->reply));
return;
}
- pc->mh = mh;
- keys = TALER_EXCHANGE_get_keys (mh);
+ keys = TALER_EXCHANGE_get_keys (exchange_handle);
if (NULL == keys)
{
GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK */
@@ -1132,12 +740,6 @@ process_pay_with_exchange (void *cls,
return;
}
- GNUNET_log (
- GNUNET_ERROR_TYPE_DEBUG,
- "Found transaction data for proposal `%s' of merchant `%s', initiating deposits\n",
- GNUNET_h2s (&pc->h_contract_terms),
- TALER_B2S (&pc->mi->pubkey));
-
/* Initiate /deposit operation for all coins of
the current exchange (!) */
GNUNET_assert (0 == pc->pending_at_ce);
@@ -1146,22 +748,21 @@ process_pay_with_exchange (void *cls,
struct DepositConfirmation *dc = &pc->dc[i];
const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
enum TALER_ErrorCode ec;
- unsigned int hc;
+ unsigned int http_status;
if (NULL != dc->dh)
continue; /* we were here before (can happen due to
tried_force_keys logic), don't go again */
- if (GNUNET_YES == dc->found_in_db)
+ if (dc->found_in_db)
continue;
if (0 != strcmp (dc->exchange_url,
pc->current_exchange))
continue;
- denom_details = TALER_EXCHANGE_get_denomination_key (keys,
- &dc->denom);
+ denom_details
+ = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+ &dc->h_denom);
if (NULL == denom_details)
{
- struct GNUNET_HashCode h_denom;
-
if (! pc->tried_force_keys)
{
/* let's try *forcing* a re-download of /keys from the exchange.
@@ -1176,8 +777,6 @@ process_pay_with_exchange (void *cls,
return;
}
/* Forcing failed or we already did it, give up */
- GNUNET_CRYPTO_rsa_public_key_hash (dc->denom.rsa_public_key,
- &h_denom);
resume_pay_with_response (
pc,
MHD_HTTP_FAILED_DEPENDENCY,
@@ -1185,49 +784,47 @@ process_pay_with_exchange (void *cls,
"{s:s, s:I, s:o, s:o}",
"hint", "coin's denomination not found",
"code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
- "h_denom_pub", GNUNET_JSON_from_data_auto (&h_denom),
- "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh)));
+ "h_denom_pub", GNUNET_JSON_from_data_auto (&dc->h_denom),
+ "exchange_keys", TALER_EXCHANGE_get_keys_raw (exchange_handle)));
return;
}
if (GNUNET_OK !=
- TMH_AUDITORS_check_dk (mh,
+ TMH_AUDITORS_check_dk (exchange_handle,
denom_details,
exchange_trusted,
- &hc,
+ &http_status,
&ec))
{
resume_pay_with_response (
pc,
- hc,
- TALER_MHD_make_json_pack ("{s:s, s:I, s:o}",
- "hint", "denomination not accepted",
- "code", (json_int_t) ec,
- "h_denom_pub", GNUNET_JSON_from_data_auto (
- &denom_details->h_key)));
+ http_status,
+ TALER_MHD_make_json_pack (
+ "{s:s, s:I, s:o}",
+ "hint",
+ "denomination not accepted",
+ "code",
+ (json_int_t) ec,
+ "h_denom_pub",
+ GNUNET_JSON_from_data_auto (&denom_details->h_key)));
return;
}
dc->deposit_fee = denom_details->fee_deposit;
dc->refund_fee = denom_details->fee_refund;
dc->wire_fee = *wire_fee;
-
GNUNET_assert (NULL != pc->wm);
GNUNET_assert (NULL != pc->wm->j_wire);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Timing for this payment, wire_deadline: %llu, refund_deadline: %llu\n",
- (unsigned long long) pc->wire_transfer_deadline.abs_value_us,
- (unsigned long long) pc->refund_deadline.abs_value_us);
- db->preflight (db->cls);
- dc->dh = TALER_EXCHANGE_deposit (mh,
+ TMH_db->preflight (TMH_db->cls);
+ dc->dh = TALER_EXCHANGE_deposit (exchange_handle,
&dc->amount_with_fee,
pc->wire_transfer_deadline,
pc->wm->j_wire,
&pc->h_contract_terms,
&dc->coin_pub,
&dc->ub_sig,
- &dc->denom,
+ &denom_details->key,
pc->timestamp,
- &pc->mi->pubkey,
+ &hc->instance->merchant_pub,
pc->refund_deadline,
&dc->coin_sig,
&deposit_cb,
@@ -1264,13 +861,14 @@ process_pay_with_exchange (void *cls,
static void
find_next_exchange (struct PayContext *pc)
{
+ GNUNET_assert (0 == pc->pending_at_ce);
for (unsigned int i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
- if (GNUNET_YES != dc->found_in_db)
+ if (! dc->found_in_db)
{
- db->preflight (db->cls);
+ TMH_db->preflight (TMH_db->cls);
pc->current_exchange = dc->exchange_url;
pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
pc->wm->wire_method,
@@ -1290,40 +888,15 @@ find_next_exchange (struct PayContext *pc)
}
}
pc->current_exchange = NULL;
- db->preflight (db->cls);
+ TMH_db->preflight (TMH_db->cls);
/* We are done with all the HTTP requests, go back and try
the 'big' database transaction! (It should work now!) */
+ GNUNET_assert (0 == pc->pending);
begin_transaction (pc);
}
/**
- * Handle a timeout for the processing of the pay request.
- *
- * @param cls our `struct PayContext`
- */
-static void
-handle_pay_timeout (void *cls)
-{
- struct PayContext *pc = cls;
-
- pc->timeout_task = NULL;
- GNUNET_assert (GNUNET_YES == pc->suspended);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Resuming /pay with error after timeout\n");
- if (NULL != pc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (pc->fo);
- pc->fo = NULL;
- }
- resume_pay_with_error (pc,
- MHD_HTTP_REQUEST_TIMEOUT,
- TALER_EC_PAY_EXCHANGE_TIMEOUT,
- "likely the exchange did not reply quickly enough");
-}
-
-
-/**
* Function called with information about a coin that was deposited.
*
* @param cls closure
@@ -1338,461 +911,347 @@ handle_pay_timeout (void *cls)
*/
static void
check_coin_paid (void *cls,
- const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *exchange_url,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee,
- const json_t *exchange_proof)
+ const struct TALER_Amount *wire_fee)
{
struct PayContext *pc = cls;
- if (0 != GNUNET_memcmp (&pc->h_contract_terms,
- h_contract_terms))
- {
- GNUNET_break (0);
- return;
- }
for (unsigned int i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
- if (GNUNET_YES == dc->found_in_db)
- continue; /* processed earlier */
-
+ if (dc->found_in_db)
+ continue; /* processed earlier, skip "expensive" memcmp() */
/* Get matching coin from results*/
if ( (0 != GNUNET_memcmp (coin_pub,
&dc->coin_pub)) ||
(0 != TALER_amount_cmp (amount_with_fee,
&dc->amount_with_fee)) )
- continue;
+ continue; /* does not match, skip */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Coin (%s) already found in our DB.\n",
- TALER_b2s (coin_pub,
- sizeof (*coin_pub)));
- if (0 >
- TALER_amount_add (&pc->total_paid,
- &pc->total_paid,
- amount_with_fee))
- {
- /* We accepted this coin for payment on this contract before,
- and now we can't even add the amount!? */
- GNUNET_break (0);
- continue;
- }
- if (0 >
- TALER_amount_add (&pc->total_fees_paid,
- &pc->total_fees_paid,
- deposit_fee))
- {
- /* We accepted this coin for payment on this contract before,
- and now we can't even add the amount!? */
- GNUNET_break (0);
- continue;
- }
+ "Deposit of coin `%s' already in our DB.\n",
+ TALER_B2S (coin_pub));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pc->total_paid,
+ &pc->total_paid,
+ amount_with_fee));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pc->total_fees_paid,
+ &pc->total_fees_paid,
+ deposit_fee));
dc->deposit_fee = *deposit_fee;
dc->refund_fee = *refund_fee;
dc->wire_fee = *wire_fee;
dc->amount_with_fee = *amount_with_fee;
- dc->found_in_db = GNUNET_YES;
+ dc->found_in_db = true;
pc->pending--;
}
}
/**
- * Try to parse the pay request into the given pay context.
- * Schedules an error response in the connection on failure.
+ * Function called with information about a refund. Check if this coin was
+ * claimed by the wallet for the transaction, and if so add the refunded
+ * amount to the pc's "total_refunded" amount.
*
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- * #GNUNET_NO on failure (response was queued with MHD)
- * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ * @param cls closure with a `struct PayContext`
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
*/
-static enum GNUNET_GenericReturnValue
-parse_pay (struct MHD_Connection *connection,
- const json_t *root,
- struct PayContext *pc)
+static void
+check_coin_refunded (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *refund_amount)
{
- json_t *coins;
- const char *order_id;
- const char *mode;
- struct TALER_MerchantPublicKeyP merchant_pub;
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("mode",
- &mode),
- GNUNET_JSON_spec_json ("coins",
- &coins),
- GNUNET_JSON_spec_string ("order_id",
- &order_id),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_pub),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_DB_QueryStatus qs;
+ struct PayContext *pc = cls;
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_YES != res)
+ for (unsigned int i = 0; i<pc->coins_cnt; i++)
{
- GNUNET_break_op (0);
- return res;
- }
+ struct DepositConfirmation *dc = &pc->dc[i];
- if (0 != GNUNET_memcmp (&merchant_pub,
- &pc->mi->pubkey))
- {
- GNUNET_JSON_parse_free (spec);
- TALER_LOG_INFO (
- "Unknown merchant public key included in payment (usually wrong instance chosen)\n");
- return
- (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PAY_WRONG_INSTANCE,
- "merchant_pub in contract does not match this instance"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ /* Get matching coin from results*/
+ if (0 != GNUNET_memcmp (coin_pub,
+ &dc->coin_pub))
+ continue;
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pc->total_refunded,
+ &pc->total_refunded,
+ refund_amount));
}
+}
- {
- const char *session_id;
- session_id = json_string_value (json_object_get (root,
- "session_id"));
- if (NULL != session_id)
- pc->session_id = GNUNET_strdup (session_id);
- }
- GNUNET_assert (NULL == pc->order_id);
- pc->order_id = GNUNET_strdup (order_id);
- GNUNET_assert (NULL == pc->contract_terms);
- qs = db->find_contract_terms (db->cls,
- &pc->contract_terms,
- order_id,
- &merchant_pub);
- if (0 > qs)
- {
- GNUNET_JSON_parse_free (spec);
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return
- (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_FETCH_PAY_ERROR,
- "Failed to obtain contract terms from DB"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_JSON_parse_free (spec);
- return
- (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_PAY_PROPOSAL_NOT_FOUND,
- "Proposal not found"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
+/**
+ * Check whether the amount paid is sufficient to cover the price.
+ *
+ * @param pc payment context to check
+ * @return true if the payment is sufficient, false if it is
+ * insufficient
+ */
+static bool
+check_payment_sufficient (struct PayContext *pc)
+{
+ struct TALER_Amount acc_fee;
+ struct TALER_Amount acc_amount;
+ struct TALER_Amount final_amount;
+ struct TALER_Amount wire_fee_delta;
+ struct TALER_Amount wire_fee_customer_contribution;
+ struct TALER_Amount total_wire_fee;
+ struct TALER_Amount total_needed;
- if (GNUNET_OK !=
- TALER_JSON_hash (pc->contract_terms,
- &pc->h_contract_terms))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return
- (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH,
- "Failed to hash proposal"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling /pay for order `%s' with contract hash `%s'\n",
- order_id,
- GNUNET_h2s (&pc->h_contract_terms));
+ GNUNET_assert (0 != pc->coins_cnt);
+ acc_fee = pc->dc[0].deposit_fee;
+ total_wire_fee = pc->dc[0].wire_fee;
+ acc_amount = pc->dc[0].amount_with_fee;
- if (NULL == json_object_get (pc->contract_terms,
- "merchant"))
- {
- /* invalid contract */
- GNUNET_JSON_parse_free (spec);
- return
- (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_MERCHANT_FIELD_MISSING,
- "No merchant field in proposal"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (0 != strcasecmp ("abort-refund",
- mode))
- pc->mode = PC_MODE_PAY;
- else
- pc->mode = PC_MODE_ABORT_REFUND;
+ /**
+ * This loops calculates what are the deposit fee / total
+ * amount with fee / and wire fee, for all the coins.
+ */
+ for (unsigned int i = 1; i<pc->coins_cnt; i++)
{
- const char *fulfillment_url;
- struct GNUNET_JSON_Specification espec[] = {
- GNUNET_JSON_spec_absolute_time ("refund_deadline",
- &pc->refund_deadline),
- GNUNET_JSON_spec_absolute_time ("pay_deadline",
- &pc->pay_deadline),
- GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
- &pc->wire_transfer_deadline),
- GNUNET_JSON_spec_absolute_time ("timestamp",
- &pc->timestamp),
- TALER_JSON_spec_amount ("max_fee",
- &pc->max_fee),
- TALER_JSON_spec_amount ("amount",
- &pc->amount),
- GNUNET_JSON_spec_string ("fulfillment_url",
- &fulfillment_url),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &pc->h_wire),
- GNUNET_JSON_spec_end ()
- };
+ struct DepositConfirmation *dc = &pc->dc[i];
- res = TALER_MHD_parse_json_data (connection,
- pc->contract_terms,
- espec);
- if (GNUNET_YES != res)
+ GNUNET_assert (dc->found_in_db);
+ if ( (0 >
+ TALER_amount_add (&acc_fee,
+ &dc->deposit_fee,
+ &acc_fee)) ||
+ (0 >
+ TALER_amount_add (&acc_amount,
+ &dc->amount_with_fee,
+ &acc_amount)) )
{
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return res;
+ /* Overflow in these amounts? Very strange. */
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts");
}
-
- pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
- if (pc->wire_transfer_deadline.abs_value_us <
- pc->refund_deadline.abs_value_us)
+ if (1 ==
+ TALER_amount_cmp (&dc->deposit_fee,
+ &dc->amount_with_fee))
{
- /* This should already have been checked when creating the
- order! */
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
- "refund deadline after wire transfer deadline");
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_PAY_FEES_EXCEED_PAYMENT,
+ "Deposit fees exceed coin's contribution");
+ return false;
}
- if (pc->pay_deadline.abs_value_us <
- GNUNET_TIME_absolute_get ().abs_value_us)
+ /* If exchange differs, add wire fee */
{
- /* too late */
- GNUNET_JSON_parse_free (spec);
- return
- (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_PAY_OFFER_EXPIRED,
- "The payment deadline has past and the offer is no longer valid"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- }
-
- /* find wire method */
- {
- struct WireMethod *wm;
+ bool new_exchange = true;
- wm = pc->mi->wm_head;
- while (0 != GNUNET_memcmp (&pc->h_wire,
- &wm->h_wire))
- wm = wm->next;
- if (NULL == wm)
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_WIRE_HASH_UNKNOWN,
- "Did not find matching wire details");
+ for (unsigned int j = 0; j<i; j++)
+ if (0 == strcasecmp (dc->exchange_url,
+ pc->dc[j].exchange_url))
+ {
+ new_exchange = false;
+ break;
+ }
+ if (new_exchange)
+ {
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&total_wire_fee,
+ &dc->wire_fee))
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
+ "exchange wire in different currency");
+ return false;
+ }
+ if (0 >
+ TALER_amount_add (&total_wire_fee,
+ &total_wire_fee,
+ &dc->wire_fee))
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
+ "could not add exchange wire fee to total");
+ return false;
+ }
+ }
}
- pc->wm = wm;
}
- /* parse optional details */
- if (NULL != json_object_get (pc->contract_terms,
- "max_wire_fee"))
- {
- struct GNUNET_JSON_Specification espec[] = {
- TALER_JSON_spec_amount ("max_wire_fee",
- &pc->max_wire_fee),
- GNUNET_JSON_spec_end ()
- };
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Amount received from wallet: %s\n",
+ TALER_amount2s (&acc_amount));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Deposit fee for all coins: %s\n",
+ TALER_amount2s (&acc_fee));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Total wire fee: %s\n",
+ TALER_amount2s (&total_wire_fee));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Max wire fee: %s\n",
+ TALER_amount2s (&pc->max_wire_fee));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Deposit fee limit for merchant: %s\n",
+ TALER_amount2s (&pc->max_fee));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Total refunded amount: %s\n",
+ TALER_amount2s (&pc->total_refunded));
- res = TALER_MHD_parse_json_data (connection,
- pc->contract_terms,
- espec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0); /* invalid input, fail */
- GNUNET_JSON_parse_free (spec);
- return res;
- }
+ /* Now compare exchange wire fee compared to
+ * what we are willing to pay */
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&total_wire_fee,
+ &pc->max_wire_fee))
+ {
+ resume_pay_with_error (pc,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
+ "exchange wire does not match our currency");
+ return false;
}
- else
+
+ switch (TALER_amount_subtract (&wire_fee_delta,
+ &total_wire_fee,
+ &pc->max_wire_fee))
{
- /* default is we cover no fee */
+ case TALER_AAR_RESULT_POSITIVE:
+ /* Actual wire fee is indeed higher than our maximum,
+ compute how much the customer is expected to cover! */
+ TALER_amount_divide (&wire_fee_customer_contribution,
+ &wire_fee_delta,
+ pc->wire_fee_amortization);
+ break;
+ case TALER_AAR_RESULT_ZERO:
+ case TALER_AAR_INVALID_NEGATIVE_RESULT:
+ /* Wire fee threshold is still above the wire fee amount.
+ Customer is not going to contribute on this. */
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (pc->max_fee.currency,
- &pc->max_wire_fee));
+ TALER_amount_get_zero (total_wire_fee.currency,
+ &wire_fee_customer_contribution));
+ break;
+ default:
+ GNUNET_assert (0);
}
- if (NULL != json_object_get (pc->contract_terms,
- "wire_fee_amortization"))
+ /* add wire fee contribution to the total fees */
+ if (0 >
+ TALER_amount_add (&acc_fee,
+ &acc_fee,
+ &wire_fee_customer_contribution))
{
- struct GNUNET_JSON_Specification espec[] = {
- GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
- &pc->wire_fee_amortization),
- GNUNET_JSON_spec_end ()
- };
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts");
+ return false;
+ }
+ if (-1 == TALER_amount_cmp (&pc->max_fee,
+ &acc_fee))
+ {
+ /**
+ * Sum of fees of *all* the different exchanges of all the coins are
+ * higher than the fixed limit that the merchant is willing to pay. The
+ * difference must be paid by the customer.
+ *///
+ struct TALER_Amount excess_fee;
- res = TALER_MHD_parse_json_data (connection,
- pc->contract_terms,
- espec);
- if ( (GNUNET_YES != res) ||
- (0 == pc->wire_fee_amortization) )
+ /* compute fee amount to be covered by customer */
+ GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
+ TALER_amount_subtract (&excess_fee,
+ &acc_fee,
+ &pc->max_fee));
+ /* add that to the total */
+ if (0 >
+ TALER_amount_add (&total_needed,
+ &excess_fee,
+ &pc->amount))
{
- GNUNET_break_op (0); /* invalid input, use default */
- /* default is no amortization */
- pc->wire_fee_amortization = 1;
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts");
+ return false;
}
}
else
{
- pc->wire_fee_amortization = 1;
+ /* Fees are fully covered by the merchant, all we require
+ is that the total payment is not below the contract's amount */
+ total_needed = pc->amount;
}
- pc->coins_cnt = json_array_size (coins);
- if (0 == pc->coins_cnt)
+ /* Do not count refunds towards the payment */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Subtracting total refunds from paid amount: %s\n",
+ TALER_amount2s (&pc->total_refunded));
+ if (0 >
+ TALER_amount_subtract (&final_amount,
+ &acc_amount,
+ &pc->total_refunded))
{
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PAY_COINS_ARRAY_EMPTY,
- "coins");
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_REFUNDS_EXCEED_PAYMENTS,
+ "refunded amount exceeds total payments");
+ return false;
}
- /* note: 1 coin = 1 deposit confirmation expected */
- pc->dc = GNUNET_new_array (pc->coins_cnt,
- struct DepositConfirmation);
- /* This loop populates the array 'dc' in 'pc' */
+ if (-1 == TALER_amount_cmp (&final_amount,
+ &total_needed))
{
- unsigned int coins_index;
- json_t *coin;
- json_array_foreach (coins, coins_index, coin)
+ /* acc_amount < total_needed */
+ if (-1 < TALER_amount_cmp (&acc_amount,
+ &total_needed))
{
- struct DepositConfirmation *dc = &pc->dc[coins_index];
- const char *exchange_url;
- struct GNUNET_JSON_Specification ispec[] = {
- TALER_JSON_spec_denomination_public_key ("denom_pub",
- &dc->denom),
- TALER_JSON_spec_amount ("contribution",
- &dc->amount_with_fee),
- GNUNET_JSON_spec_string ("exchange_url",
- &exchange_url),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &dc->coin_pub),
- TALER_JSON_spec_denomination_signature ("ub_sig",
- &dc->ub_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &dc->coin_sig),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (connection,
- coin,
- ispec);
- if (GNUNET_YES != res)
- {
- GNUNET_JSON_parse_free (spec);
- GNUNET_break_op (0);
- return res;
- }
- dc->exchange_url = GNUNET_strdup (exchange_url);
- dc->index = coins_index;
- dc->pc = pc;
+ resume_pay_with_error (pc,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ TALER_EC_PAY_REFUNDED,
+ "contract not paid up due to refunds");
}
- }
- pc->pending = pc->coins_cnt;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with information about a refund.
- * Check if this coin was claimed by the wallet for the
- * transaction, and if so add the refunded amount to the
- * pc's "total_refunded" amount.
- *
- * @param cls closure with a `struct PayContext`
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
- */
-static void
-check_coin_refunded (void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *exchange_url,
- uint64_t rtransaction_id,
- const char *reason,
- const struct TALER_Amount *refund_amount,
- const struct TALER_Amount *refund_fee)
-{
- struct PayContext *pc = cls;
-
- (void) exchange_url;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
- {
- struct DepositConfirmation *dc = &pc->dc[i];
-
- /* Get matching coin from results*/
- if (0 == GNUNET_memcmp (coin_pub,
- &dc->coin_pub))
+ else if (-1 < TALER_amount_cmp (&acc_amount,
+ &pc->amount))
{
- dc->refunded = GNUNET_YES;
- GNUNET_assert (0 <=
- TALER_amount_add (&pc->total_refunded,
- &pc->total_refunded,
- refund_amount));
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
+ "contract not paid up due to fees (client may have calculated them badly)");
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ TALER_EC_PAY_PAYMENT_INSUFFICIENT,
+ "payment insufficient");
}
+ return false;
}
+ return true;
}
/**
- * Begin of the DB transaction. If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Begin of the DB transaction for a payment. If required (from
+ * soft/serialization errors), the transaction can be restarted here.
*
- * @param pc payment context to transact
+ * @param[in,out] pc payment context to transact
*/
static void
begin_transaction (struct PayContext *pc)
{
enum GNUNET_DB_QueryStatus qs;
+ struct TMH_HandlerContext *hc = pc->hc;
+ const char *instance_id = hc->instance->settings.id;
/* Avoid re-trying transactions on soft errors forever! */
if (pc->retry_counter++ > MAX_RETRIES)
@@ -1806,7 +1265,9 @@ begin_transaction (struct PayContext *pc)
}
GNUNET_assert (GNUNET_YES == pc->suspended);
- /* Init. some price accumulators. */
+ /* Initialize some amount accumulators
+ (used in check_coin_paid(), check_coin_refunded()
+ and check_payment_sufficient()). */
GNUNET_break (GNUNET_OK ==
TALER_amount_get_zero (pc->amount.currency,
&pc->total_paid));
@@ -1816,12 +1277,15 @@ begin_transaction (struct PayContext *pc)
GNUNET_break (GNUNET_OK ==
TALER_amount_get_zero (pc->amount.currency,
&pc->total_refunded));
+ for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ pc->dc[i].found_in_db = false;
+ pc->pending = pc->coins_cnt;
/* First, try to see if we have all we need already done */
- db->preflight (db->cls);
+ TMH_db->preflight (TMH_db->cls);
if (GNUNET_OK !=
- db->start (db->cls,
- "run pay"))
+ TMH_db->start (TMH_db->cls,
+ "run pay"))
{
GNUNET_break (0);
resume_pay_with_error (pc,
@@ -1832,14 +1296,14 @@ begin_transaction (struct PayContext *pc)
}
/* Check if some of these coins already succeeded for _this_ contract. */
- qs = db->find_payments (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey,
- &check_coin_paid,
- pc);
+ qs = TMH_db->lookup_deposits (TMH_db->cls,
+ instance_id,
+ &pc->h_contract_terms,
+ &check_coin_paid,
+ pc);
if (0 > qs)
{
- db->rollback (db->cls);
+ TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (pc);
@@ -1855,14 +1319,14 @@ begin_transaction (struct PayContext *pc)
}
/* Check if we refunded some of the coins */
- qs = db->get_refunds_from_contract_terms_hash (db->cls,
- &pc->mi->pubkey,
- &pc->h_contract_terms,
- &check_coin_refunded,
- pc);
+ qs = TMH_db->lookup_refunds (TMH_db->cls,
+ instance_id,
+ &pc->h_contract_terms,
+ &check_coin_refunded,
+ pc);
if (0 > qs)
{
- db->rollback (db->cls);
+ TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
begin_transaction (pc);
@@ -1877,339 +1341,468 @@ begin_transaction (struct PayContext *pc)
return;
}
- /* All the coins known to the database have
- * been processed, now delve into specific case
- * (pay vs. abort) */
+ /* Check if there are coins that still need to be processed */
- if (PC_MODE_ABORT_REFUND == pc->mode)
+ if (0 != pc->pending)
{
- json_t *terms;
+ /* we made no DB changes, so we can just rollback */
+ TMH_db->rollback (TMH_db->cls);
- /* The wallet is going for a refund,
- (on aborted operation)! */
+ /* Ok, we need to first go to the network to process more coins.
+ We that interaction in *tiny* transactions (hence the rollback
+ above). */
+ find_next_exchange (pc);
+ return;
+ }
- /* check payment was indeed incomplete */
- qs = db->find_paid_contract_terms_from_hash (db->cls,
- &terms,
- &pc->h_contract_terms,
- &pc->mi->pubkey);
- if (0 > qs)
- {
- db->rollback (db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- begin_transaction (pc);
- return;
- }
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_STORE_PAY_ERROR,
- "Merchant database error");
- return;
- }
- if (0 < qs)
+ /* 0 == pc->pending: all coins processed, let's see if that was enough */
+ if (! check_payment_sufficient (pc))
+ {
+ /* check_payment_sufficient() will have queued an error already.
+ We need to still abort the transaction. */
+ TMH_db->rollback (TMH_db->cls);
+ return;
+ }
+ /* Payment succeeded, save in database */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' (%s) was fully paid\n",
+ pc->order_id,
+ GNUNET_h2s (&pc->h_contract_terms));
+ qs = TMH_db->mark_contract_paid (TMH_db->cls,
+ instance_id,
+ &pc->h_contract_terms,
+ pc->session_id);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- /* Payment had been complete! */
- json_decref (terms);
- db->rollback (db->cls);
- resume_pay_with_error (pc,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_PAY_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
- "Payment complete, refusing to abort");
+ begin_transaction (pc);
return;
}
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+ "Could not set contract to 'paid' in DB");
+ return;
+ }
- /* Store refund in DB */
- qs = db->increase_refund_for_contract_NT (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey,
- &pc->total_paid,
- /* justification */
- "incomplete payment aborted");
- if (0 > qs)
+ /* Now commit! */
+ if (0 <= qs)
+ qs = TMH_db->commit (TMH_db->cls);
+ else
+ TMH_db->rollback (TMH_db->cls);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- db->rollback (db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- begin_transaction (pc);
- return;
- }
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_STORE_PAY_ERROR,
- "Merchant database error storing abort-refund");
+ begin_transaction (pc);
return;
}
- qs = db->commit (db->cls);
- if (0 > qs)
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+ "Database commit mark proposal as 'paid' failed");
+ return;
+ }
+
+ /* Notify clients that have been waiting for the payment to succeed */
+ TMH_long_poll_resume (pc->order_id,
+ hc->instance,
+ NULL);
+
+ /* Generate response (payment successful) */
+ {
+ struct GNUNET_CRYPTO_EddsaSignature sig;
+
+ /* Sign on our end (as the payment did go through, even if it may
+ have been refunded already) */
{
- db->rollback (db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- begin_transaction (pc);
- return;
- }
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_STORE_PAY_ERROR,
- "Merchant database error: could not commit");
- return;
+ struct PaymentResponsePS mr = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+ .purpose.size = htonl (sizeof (mr)),
+ .h_contract_terms = pc->h_contract_terms
+ };
+
+ GNUNET_CRYPTO_eddsa_sign (&pc->hc->instance->merchant_priv.eddsa_priv,
+ &mr,
+ &sig);
}
- /* At this point, the refund got correctly committed
- * into the database. */
+
+ /* Build the response */
{
- json_t *refunds;
+ json_t *resp;
- refunds = json_array ();
- if (NULL == refunds)
+ resp = json_pack ("{s:o}",
+ "sig",
+ GNUNET_JSON_from_data_auto (&sig));
+ if (NULL == resp)
{
GNUNET_break (0);
resume_pay_with_error (pc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_JSON_ALLOCATION_FAILURE,
- "could not create JSON array");
+ "Could not build final response");
return;
}
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ resume_pay_with_response (pc,
+ MHD_HTTP_OK,
+ TALER_MHD_make_json (resp));
+ json_decref (resp);
+ }
+ }
+}
+
+
+/**
+ * Try to parse the pay request into the given pay context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param connection HTTP connection we are receiving payment on
+ * @param[in,out] hc context with further information about the request
+ * @param pc context we use to handle the payment
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO on failure (response was queued with MHD)
+ * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ */
+static enum GNUNET_GenericReturnValue
+parse_pay (struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc,
+ struct PayContext *pc)
+{
+ /* First, parse request */
+ {
+ json_t *coins;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("coins",
+ &coins),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ return res;
+ }
+ }
+
+ if ( (! json_is_array (coins)) ||
+ (0 == (pc->coins_cnt = json_array_size (coins))) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_PAY_COINS_ARRAY_EMPTY,
+ "'coins' array is empty or not even an array");
+ }
+
+ /* note: 1 coin = 1 deposit confirmation expected */
+ pc->dc = GNUNET_new_array (pc->coins_cnt,
+ struct DepositConfirmation);
+
+ /* This loop populates the array 'dc' in 'pc' */
+ {
+ unsigned int coins_index;
+ json_t *coin;
+ json_array_foreach (coins, coins_index, coin)
{
- struct TALER_MerchantSignatureP msig;
- struct TALER_RefundRequestPS rr = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
- .purpose.size = htonl (sizeof (rr)),
- .h_contract_terms = pc->h_contract_terms,
- .coin_pub = pc->dc[i].coin_pub,
- .merchant = pc->mi->pubkey,
- .rtransaction_id = GNUNET_htonll (0)
+ struct DepositConfirmation *dc = &pc->dc[coins_index];
+ const char *exchange_url;
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom",
+ &dc->h_denom),
+ TALER_JSON_spec_amount ("contribution",
+ &dc->amount_with_fee),
+ GNUNET_JSON_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &dc->coin_pub),
+ TALER_JSON_spec_denomination_signature ("ub_sig",
+ &dc->ub_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &dc->coin_sig),
+ GNUNET_JSON_spec_end ()
};
- if (GNUNET_YES != pc->dc[i].found_in_db)
- continue; /* Skip coins not found in DB. */
- TALER_amount_hton (&rr.refund_amount,
- &pc->dc[i].amount_with_fee);
- TALER_amount_hton (&rr.refund_fee,
- &pc->dc[i].refund_fee);
-
- GNUNET_CRYPTO_eddsa_sign (&pc->mi->privkey.eddsa_priv,
- &rr,
- &msig.eddsa_sig);
- /* Pack refund for i-th coin. */
- if (0 !=
- json_array_append_new (
- refunds,
- json_pack ("{s:I, s:o, s:o s:o s:o}",
- "rtransaction_id",
- (json_int_t) 0,
- "coin_pub",
- GNUNET_JSON_from_data_auto (&rr.coin_pub),
- "merchant_sig",
- GNUNET_JSON_from_data_auto (&msig),
- "refund_amount",
- TALER_JSON_from_amount_nbo (&rr.refund_amount),
- "refund_fee",
- TALER_JSON_from_amount_nbo (&rr.refund_fee))))
+ res = TALER_MHD_parse_json_data (connection,
+ coin,
+ ispec);
+ if (GNUNET_YES != res)
{
- json_decref (refunds);
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "could not create JSON array");
- return;
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_break_op (0);
+ return res;
}
+ dc->exchange_url = GNUNET_strdup (exchange_url);
+ dc->index = coins_index;
+ dc->pc = pc;
}
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
- /* Resume and send back the response. */
- resume_pay_with_response (
- pc,
- MHD_HTTP_OK,
- TALER_MHD_make_json_pack (
- "{s:o, s:o, s:o}",
- /* Refunds pack. */
- "refund_permissions", refunds,
- "merchant_pub",
- GNUNET_JSON_from_data_auto (&pc->mi->pubkey),
- "h_contract_terms",
- GNUNET_JSON_from_data_auto (&pc->h_contract_terms)));
+ /* copy session ID (if set) */
+ {
+ const char *session_id;
+ json_t *sid;
+
+ sid = json_object_get (hc->request_body,
+ "session_id");
+ if (NULL != sid)
+ {
+ if (! json_is_string (sid))
+ {
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_PARAMETER_MALFORMED,
+ "session_id"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ session_id = json_string_value (sid);
+ GNUNET_assert (NULL != session_id);
+ pc->session_id = GNUNET_strdup (session_id);
}
- return;
- } /* End of PC_MODE_ABORT_REFUND */
+ }
+
+ /* copy order ID */
+ {
+ const char *order_id = hc->infix;
- /* Default PC_MODE_PAY mode */
+ GNUNET_assert (NULL != order_id);
+ GNUNET_assert (NULL == pc->order_id);
+ pc->order_id = GNUNET_strdup (order_id);
+ }
- /* Final termination case: all coins already known, just
- generate ultimate outcome. */
- if (0 == pc->pending)
+ /* obtain contract terms */
{
- if (GNUNET_OK != check_payment_sufficient (pc))
+ enum GNUNET_DB_QueryStatus qs;
+ json_t *contract_terms = NULL;
+
+ qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+ hc->instance->settings.id,
+ pc->order_id,
+ &contract_terms);
+ if (0 > qs)
{
- db->rollback (db->cls);
- return;
+ /* single, read-only SQL statements should never cause
+ serialization problems */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ /* Always report on hard error to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_FETCH_PAY_ERROR,
+ "Failed to obtain contract terms from DB"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_PAY_PROPOSAL_NOT_FOUND,
+ "Proposal not found"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+
+ /* hash contract (needed later) */
+ if (GNUNET_OK !=
+ TALER_JSON_hash (contract_terms,
+ &pc->h_contract_terms))
+ {
+ GNUNET_break (0);
+ json_decref (contract_terms);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH,
+ "Failed to hash contract terms"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
- /* Payment succeeded, save in database */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Contract `%s' was fully paid\n",
+ "Handling payment for order `%s' with contract hash `%s'\n",
+ pc->order_id,
GNUNET_h2s (&pc->h_contract_terms));
- qs = db->mark_proposal_paid (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey);
- if (qs < 0)
+
+ /* basic sanity check on the contract */
+ if (NULL == json_object_get (contract_terms,
+ "merchant"))
{
- db->rollback (db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ /* invalid contract */
+ GNUNET_break (0);
+ json_decref (contract_terms);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_MERCHANT_FIELD_MISSING,
+ "No merchant field in proposal"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+
+ /* Get details from contract and check fundamentals */
+ {
+ struct GNUNET_JSON_Specification espec[] = {
+ GNUNET_JSON_spec_absolute_time ("refund_deadline",
+ &pc->refund_deadline),
+ GNUNET_JSON_spec_absolute_time ("pay_deadline",
+ &pc->pay_deadline),
+ GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
+ &pc->wire_transfer_deadline),
+ GNUNET_JSON_spec_absolute_time ("timestamp",
+ &pc->timestamp),
+ TALER_JSON_spec_amount ("max_fee",
+ &pc->max_fee),
+ TALER_JSON_spec_amount ("amount",
+ &pc->amount),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &pc->h_wire),
+ GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
+ &pc->wire_fee_amortization),
+ TALER_JSON_spec_amount ("max_wire_fee",
+ &pc->max_wire_fee),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ /* FIXME: this is a tad wrong, as IF the contract is
+ malformed, the routine generates an error that blames
+ it on the client (400) instead of on us! */
+ res = TALER_MHD_parse_json_data (connection,
+ contract_terms,
+ espec);
+ json_decref (contract_terms);
+ if (GNUNET_YES != res)
{
- begin_transaction (pc);
- return;
+ GNUNET_break (0);
+ return res;
}
- resume_pay_with_error (
- pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
- "Merchant database error: could not mark proposal as 'paid'");
- return;
}
- if ( (NULL != pc->session_id) &&
- (NULL != pc->fulfillment_url) )
+ if (pc->wire_transfer_deadline.abs_value_us <
+ pc->refund_deadline.abs_value_us)
{
- qs = db->insert_session_info (db->cls,
- pc->session_id,
- pc->fulfillment_url,
- pc->order_id,
- &pc->mi->pubkey);
+ /* This should already have been checked when creating the order! */
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
+ "refund deadline after wire transfer deadline");
}
- /* Now commit! */
- if (0 <= qs)
- qs = db->commit (db->cls);
- else
- db->rollback (db->cls);
- if (0 > qs)
+ if (pc->pay_deadline.abs_value_us <
+ GNUNET_TIME_absolute_get ().abs_value_us)
{
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- begin_transaction (pc);
- return;
- }
- resume_pay_with_error (
- pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
- "Merchant database error: could not commit to mark proposal as 'paid'");
- return;
+ /* too late */
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_PAY_OFFER_EXPIRED,
+ "We are past the payment deadline"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
- TMH_long_poll_resume (pc->order_id,
- &pc->mi->pubkey,
- NULL);
- generate_success_response (pc);
- return;
}
+ /* Make sure wire method (still) exists for this instance */
+ {
+ struct TMH_WireMethod *wm;
- /* we made no DB changes,
- so we can just rollback */
- db->rollback (db->cls);
+ wm = hc->instance->wm_head;
+ while (0 != GNUNET_memcmp (&pc->h_wire,
+ &wm->h_wire))
+ wm = wm->next;
+ if (NULL == wm)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_WIRE_HASH_UNKNOWN,
+ "Did not find matching wire details");
+ }
+ pc->wm = wm;
+ }
- /* Ok, we need to first go to the network.
- Do that interaction in *tiny* transactions. */
- find_next_exchange (pc);
+ return GNUNET_OK;
}
/**
- * Process a payment for a proposal.
+ * Handle a timeout for the processing of the pay request.
*
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return value to return to MHD (#MHD_NO to drop connection,
- * #MHD_YES to keep handling it)
+ * @param cls our `struct PayContext`
*/
-static MHD_RESULT
-handler_pay_json (struct MHD_Connection *connection,
- const json_t *root,
- struct PayContext *pc)
+static void
+handle_pay_timeout (void *cls)
{
- {
- enum GNUNET_GenericReturnValue ret;
-
- ret = parse_pay (connection,
- root,
- pc);
- if (GNUNET_OK != ret)
- return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
- }
+ struct PayContext *pc = cls;
- /* Payment not finished, suspend while we interact with the exchange */
- MHD_suspend_connection (connection);
- pc->suspended = GNUNET_YES;
+ pc->timeout_task = NULL;
+ GNUNET_assert (GNUNET_YES == pc->suspended);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending /pay handling while working with the exchange\n");
- pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
- &handle_pay_timeout,
- pc);
- begin_transaction (pc);
- return MHD_YES;
+ "Resuming pay with error after timeout\n");
+ if (NULL != pc->fo)
+ {
+ TMH_EXCHANGES_find_exchange_cancel (pc->fo);
+ pc->fo = NULL;
+ }
+ resume_pay_with_error (pc,
+ MHD_HTTP_REQUEST_TIMEOUT,
+ TALER_EC_PAY_EXCHANGE_TIMEOUT,
+ "likely the exchange did not reply quickly enough");
}
/**
- * Process a payment for a proposal. Takes data from the given MHD
- * connection.
+ * Process a payment for a claimed order.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure
- * (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a
- * upload_data
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
* @return MHD result code
*/
MHD_RESULT
MH_handler_pay (struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size,
- struct MerchantInstance *mi)
+ struct TMH_HandlerContext *hc)
{
- struct PayContext *pc;
- enum GNUNET_GenericReturnValue res;
- MHD_RESULT ret;
- json_t *root;
+ struct PayContext *pc = hc->ctx;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"In handler for /pay.\n");
- if (NULL == *connection_cls)
+ if (NULL == pc)
{
pc = GNUNET_new (struct PayContext);
GNUNET_CONTAINER_DLL_insert (pc_head,
pc_tail,
pc);
- pc->hc.cc = &pay_context_cleanup;
pc->connection = connection;
- *connection_cls = pc;
- pc->mi = mi;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/pay: picked instance %s\n",
- mi->id);
- }
- else
- {
- /* not the first call, recover state */
- pc = *connection_cls;
+ pc->hc = hc;
+ hc->ctx = pc;
+ hc->cc = &pay_context_cleanup;
}
if (GNUNET_SYSERR == pc->suspended)
return MHD_NO; /* during shutdown, we don't generate any more replies */
if (0 != pc->response_code)
{
- /* We are *done* processing the request,
- just queue the response (!) */
+ MHD_RESULT res;
+
+ /* We are *done* processing the request, just queue the response (!) */
if (UINT_MAX == pc->response_code)
{
GNUNET_break (0);
@@ -2221,35 +1814,34 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
MHD_destroy_response (pc->response);
pc->response = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Queueing response (%u) for /pay (%s).\n",
+ "Queueing response (%u) for POST /orders/$ID/pay (%s).\n",
(unsigned int) pc->response_code,
res ? "OK" : "FAILED");
return res;
}
-
- res = TALER_MHD_parse_post_json (connection,
- &pc->json_parse_context,
- upload_data,
- upload_data_size,
- &root);
- if (GNUNET_SYSERR == res)
{
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_JSON_INVALID,
- "could not parse JSON");
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = parse_pay (connection,
+ hc,
+ pc);
+ if (GNUNET_OK != ret)
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
}
- if ( (GNUNET_NO == res) ||
- (NULL == root) )
- return MHD_YES; /* the POST's body has to be further fetched */
-
- ret = handler_pay_json (connection,
- root,
- pc);
- json_decref (root);
- return ret;
+
+ /* Payment not finished, suspend while we interact with the exchange */
+ GNUNET_assert (GNUNET_NO == pc->suspended);
+ MHD_suspend_connection (connection);
+ pc->suspended = GNUNET_YES;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Suspending pay handling while working with the exchange\n");
+ GNUNET_assert (NULL == pc->timeout_task);
+ pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
+ &handle_pay_timeout,
+ pc);
+ begin_transaction (pc);
+ return MHD_YES;
}
-/* end of taler-merchant-httpd_pay.c */
+/* end of taler-merchant-httpd_post-orders-ID-pay.c */
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.h b/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
index 726a27be..7cce41f8 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2017 GNUnet e.V.
+ (C) 2014-2020 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -14,12 +14,13 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file backend/taler-merchant-httpd_pay.h
- * @brief headers for /pay handler
+ * @file backend/taler-merchant-httpd_post-orders-ID-pay.h
+ * @brief headers for POST /orders/$ID/pay handler
* @author Marcello Stanisci
+ * @author Christian Grothoff
*/
-#ifndef TALER_EXCHANGE_HTTPD_PAY_H
-#define TALER_EXCHANGE_HTTPD_PAY_H
+#ifndef TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_PAY_H
+#define TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_PAY_H
#include <microhttpd.h>
#include "taler-merchant-httpd.h"
@@ -33,22 +34,16 @@ MH_force_pc_resume (void);
/**
- * Manage a payment
+ * Process payment for an order.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
* @return MHD result code
*/
MHD_RESULT
-MH_handler_pay (struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size,
- struct MerchantInstance *mi);
+TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
#endif
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index a4312c09..3335aeab 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -1391,6 +1391,7 @@ typedef void
/**
* Calls the POST /orders/$ID/claim API at the backend. That is,
* retrieve the final contract terms including the client nonce.
+ *
* This is a PUBLIC API for wallets.
*
* @param ctx execution context
@@ -1419,188 +1420,112 @@ void
TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle *och);
-/* ********************* OLD ************************** */
-
+/**
+ * @brief Handle to a POST /orders/$ID/pay operation at a merchant. Note that
+ * we use the same handle for interactions with frontends (API for wallets) or
+ * backends (API for frontends). The difference is that for the frontend API,
+ * we need the private keys of the coins, while for the backend API we need
+ * the public keys and signatures received from the wallet.
+ */
+struct TALER_MERCHANT_OrderPayHandle;
-/* ********************* /refund ************************** */
/**
- * Handle for a GET /refund operation.
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /orders/$ID/pay request to a merchant.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
*/
-struct TALER_MERCHANT_RefundLookupOperation;
+typedef void
+(*TALER_MERCHANT_OrderPayCallback) (
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
/**
- * Detail about a refund lookup result.
+ * Information we need from the frontend (ok, the frontend sends just JSON)
+ * when forwarding a payment to the backend.
*/
-struct TALER_MERCHANT_RefundDetail
+struct TALER_MERCHANT_PaidCoin
{
+ /**
+ * Denomination key with which the coin is signed
+ */
+ struct TALER_DenominationPublicKey denom_pub;
/**
- * Exchange response details. Full details are only included
- * upon failure (HTTP status is not #MHD_HTTP_OK).
+ * Exchange’s unblinded signature of the coin
*/
- struct TALER_EXCHANGE_HttpResponse hr;
+ struct TALER_DenominationSignature denom_sig;
/**
- * Coin this detail is about.
+ * Overall value that coins of this @e denom_pub have.
+ */
+ struct TALER_Amount denom_value;
+
+ /**
+ * Coin's public key.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
- * Refund transaction ID used.
+ * Coin's signature key.
*/
- uint64_t rtransaction_id;
+ struct TALER_CoinSpendSignatureP coin_sig;
/**
- * Amount to be refunded for this coin.
+ * Amount this coin contributes to (including fee).
*/
- struct TALER_Amount refund_amount;
+ struct TALER_Amount amount_with_fee;
/**
- * Applicable refund transaction fee.
+ * Amount this coin contributes to (without fee).
*/
- struct TALER_Amount refund_fee;
+ struct TALER_Amount amount_without_fee;
/**
- * Public key of the exchange affirming the refund,
- * only valid if the @e hr http_status is #MHD_HTTP_OK.
+ * Fee the exchange charges for refunds of this coin.
*/
- struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_Amount refund_fee;
/**
- * Signature of the exchange affirming the refund,
- * only valid if the @e hr http_status is #MHD_HTTP_OK.
+ * What is the URL of the exchange that issued @a coin_pub?
*/
- struct TALER_ExchangeSignatureP exchange_sig;
+ const char *exchange_url;
};
/**
- * Callback to process a GET /refund request
+ * Pay a merchant. API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures. Note the sublte difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
*
- * @param cls closure
- * @param hr HTTP response details
- * @param h_contract_terms hash of the contract terms to which the refund is applied
- * @param merchant_pub public key of the merchant
- * @param num_details length of the @a details array
- * @param details details about the refund processing
- */
-typedef void
-(*TALER_MERCHANT_RefundLookupCallback) (
- void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- unsigned int num_details,
- const struct TALER_MERCHANT_RefundDetail *details);
-
-
-/**
- * Does a GET /refund.
+ * This is a PUBLIC API, albeit in this form useful for the frontend,
+ * in case the frontend is proxying the request.
*
* @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param order_id order id used to perform the lookup
- * @param cb callback which will work the response gotten from the backend
- * @param cb_cls closure to pass to the callback
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_RefundLookupOperation *
-TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *order_id,
- TALER_MERCHANT_RefundLookupCallback cb,
- void *cb_cls);
-
-/**
- * Cancel a GET /refund request.
- *
- * @param rlo the refund increasing operation to cancel
- */
-void
-TALER_MERCHANT_refund_lookup_cancel (
- struct TALER_MERCHANT_RefundLookupOperation *rlo);
-
-
-/**
- * Handle for a POST /refund operation.
- */
-struct TALER_MERCHANT_RefundIncreaseOperation;
-
-
-/**
- * Callback to process a POST /refund request
- *
- * @param cls closure
- * @param http_status HTTP status code for this request
- * @param ec taler-specific error code
- */
-typedef void
-(*TALER_MERCHANT_RefundIncreaseCallback) (
- void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr);
-
-
-/**
- * Increase the refund associated to a order
- *
- * @param ctx the CURL context used to connect to the backend
- * @param backend_url backend's base URL, including final "/"
- * @param order_id id of the order whose refund is to be increased
- * @param refund amount to which increase the refund
- * @param reason human-readable reason justifying the refund
- * @param cb callback processing the response from /refund
- * @param cb_cls closure for cb
- */
-struct TALER_MERCHANT_RefundIncreaseOperation *
-TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
- const char *backend_url,
- const char *order_id,
- const struct TALER_Amount *refund,
- const char *reason,
- TALER_MERCHANT_RefundIncreaseCallback cb,
- void *cb_cls);
-
-/**
- * Cancel a POST /refund request.
- *
- * @param rio the refund increasing operation to cancel
- */
-void
-TALER_MERCHANT_refund_increase_cancel (
- struct TALER_MERCHANT_RefundIncreaseOperation *rio);
-
-
-/* ********************* /proposal *********************** */
-
-
-/* ********************* /pay *********************** */
-
-
-/**
- * @brief Handle to a /pay operation at a merchant. Note that we use
- * the same handle for interactions with frontends (API for wallets)
- * or backends (API for frontends). The difference is that for the
- * frontend API, we need the private keys of the coins, while for
- * the backend API we need the public keys and signatures received
- * from the wallet. Also, the frontend returns a redirect URL on
- * success, while the backend just returns a success status code.
- */
-struct TALER_MERCHANT_Pay;
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * /pay request to a merchant.
- *
- * @param cls closure
- * @param hr HTTP response details
+ * @param merchant_url base URL of the merchant
+ * @param merchant_pub public key of the merchant
+ * @param order_id which order should be paid
+ * @param num_coins length of the @a coins array
+ * @param coins array of coins to pay with
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
*/
-typedef void
-(*TALER_MERCHANT_PayCallback) (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr);
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay_frontend (
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *order_id,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PaidCoin coins[],
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls);
/**
@@ -1655,6 +1580,8 @@ struct TALER_MERCHANT_PayCoin
/**
* Pay a merchant. API for wallets that have the coin's private keys.
*
+ * This is a PUBLIC API for wallets.
+ *
* @param ctx execution context
* @param merchant_url base URL of the merchant
* @param h_wire hash of the merchant’s account details
@@ -1674,29 +1601,50 @@ struct TALER_MERCHANT_PayCoin
* @param pay_cb_cls closure for @a pay_cb
* @return a handle for this request
*/
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
- const struct GNUNET_HashCode *h_contract,
- const struct TALER_Amount *amount,
- const struct TALER_Amount *max_fee,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantSignatureP *merchant_sig,
- struct GNUNET_TIME_Absolute timestamp,
- struct GNUNET_TIME_Absolute refund_deadline,
- struct GNUNET_TIME_Absolute pay_deadline,
- const struct GNUNET_HashCode *h_wire,
- const char *order_id,
- unsigned int num_coins,
- const struct TALER_MERCHANT_PayCoin *coins,
- TALER_MERCHANT_PayCallback pay_cb,
- void *pay_cb_cls);
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay (
+ struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const struct GNUNET_HashCode *h_contract,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ struct GNUNET_TIME_Absolute pay_deadline,
+ const struct GNUNET_HashCode *h_wire,
+ const char *order_id,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PayCoin coins[],
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls);
+
+
+/**
+ * Cancel a POST /orders/$ID/pay request. Note that if you cancel a request
+ * like this, you have no assurance that the request has not yet been
+ * forwarded to the merchant. Thus, the payment may still succeed or fail.
+ * Re-issue the original /pay request to resume/retry and obtain a definitive
+ * result, or refresh the coins involved to ensure that the merchant can no
+ * longer complete the payment.
+ *
+ * @param oph the payment request handle
+ */
+void
+TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph);
+
+
+/**
+ * Handle for an POST /orders/$ID/abort operation.
+ */
+struct TALER_MERCHANT_AbortHandle;
/**
* Entry in the array of refunded coins.
*/
-struct TALER_MERCHANT_RefundEntry
+struct TALER_MERCHANT_AbortedCoin
{
/**
* Merchant signature affirming the refund.
@@ -1717,28 +1665,31 @@ struct TALER_MERCHANT_RefundEntry
/**
* Callbacks of this type are used to serve the result of submitting a
- * /pay request to a merchant.
+ * /orders/$ID/abort request to a merchant.
*
* @param cls closure
* @param hr HTTP response details
* @param merchant_pub public key of the merchant
* @param h_contract hash of the contract
- * @param num_refunds size of the @a res array, 0 on errors
- * @param res merchant signatures refunding coins, NULL on errors
+ * @param num_aborts size of the @a res array, 0 on errors
+ * @param aborts merchant signatures refunding coins, NULL on errors
*/
typedef void
-(*TALER_MERCHANT_PayRefundCallback) (
+(*TALER_MERCHANT_PayAbortCallback) (
void *cls,
const struct TALER_MERCHANT_HttpResponse *hr,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct GNUNET_HashCode *h_contract,
- unsigned int num_refunds,
- const struct TALER_MERCHANT_RefundEntry *res);
+ unsigned int num_aborts,
+ const struct TALER_MERCHANT_AbortedCoin aborts[]);
/**
- * Run a payment abort operation, asking for refunds for coins
- * that were previously spend on a /pay that failed to go through.
+ * Run a payment abort operation, asking for the payment to be aborted,
+ * yieldingrefunds for coins that were previously spend on a payment that
+ * failed to go through.
+ *
+ * This is a PUBLIC API for wallets.
*
* @param ctx execution context
* @param merchant_url base URL of the merchant
@@ -1759,119 +1710,194 @@ typedef void
* @param payref_cb_cls closure for @a pay_cb
* @return a handle for this request
*/
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_abort (struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
- const struct GNUNET_HashCode *h_contract,
- const struct TALER_Amount *amount,
- const struct TALER_Amount *max_fee,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_MerchantSignatureP *merchant_sig,
- struct GNUNET_TIME_Absolute timestamp,
- struct GNUNET_TIME_Absolute refund_deadline,
- struct GNUNET_TIME_Absolute pay_deadline,
- const struct GNUNET_HashCode *h_wire,
- const char *order_id,
- unsigned int num_coins,
- const struct TALER_MERCHANT_PayCoin *coins,
- TALER_MERCHANT_PayRefundCallback payref_cb,
- void *payref_cb_cls);
+struct TALER_MERCHANT_AbortHandle *
+TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
+ const char *merchant_url,
+ const struct GNUNET_HashCode *h_contract,
+ const struct TALER_Amount *amount,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_MerchantSignatureP *merchant_sig,
+ struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ struct GNUNET_TIME_Absolute pay_deadline,
+ const struct GNUNET_HashCode *h_wire,
+ const char *order_id,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PayCoin coins[],
+ TALER_MERCHANT_PayAbortCallback cb,
+ void *cb_cls);
/**
- * Information we need from the frontend (ok, the frontend sends just JSON)
- * when forwarding a payment to the backend.
+ * Cancel a POST /orders/$ID/abort request. Note that if you cancel a request
+ * like this, you have no assurance that the request has not yet been
+ * forwarded to the merchant.
+ *
+ * @param oah the abort request handle
*/
-struct TALER_MERCHANT_PaidCoin
-{
- /**
- * Denomination key with which the coin is signed
- */
- struct TALER_DenominationPublicKey denom_pub;
+void
+TALER_MERCHANT_order_abort_cancel (struct TALER_MERCHANT_OrderAbortandle *oah);
- /**
- * Exchange’s unblinded signature of the coin
- */
- struct TALER_DenominationSignature denom_sig;
+
+/* ********************* OLD ************************** */
+
+
+/* ********************* /refund ************************** */
+
+/**
+ * Handle for a GET /refund operation.
+ */
+struct TALER_MERCHANT_RefundLookupOperation;
+
+
+/**
+ * Detail about a refund lookup result.
+ */
+struct TALER_MERCHANT_RefundDetail
+{
/**
- * Overall value that coins of this @e denom_pub have.
+ * Exchange response details. Full details are only included
+ * upon failure (HTTP status is not #MHD_HTTP_OK).
*/
- struct TALER_Amount denom_value;
+ struct TALER_EXCHANGE_HttpResponse hr;
/**
- * Coin's public key.
+ * Coin this detail is about.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
- * Coin's signature key.
+ * Refund transaction ID used.
*/
- struct TALER_CoinSpendSignatureP coin_sig;
+ uint64_t rtransaction_id;
/**
- * Amount this coin contributes to (including fee).
+ * Amount to be refunded for this coin.
*/
- struct TALER_Amount amount_with_fee;
+ struct TALER_Amount refund_amount;
/**
- * Amount this coin contributes to (without fee).
+ * Applicable refund transaction fee.
*/
- struct TALER_Amount amount_without_fee;
+ struct TALER_Amount refund_fee;
/**
- * Fee the exchange charges for refunds of this coin.
+ * Public key of the exchange affirming the refund,
+ * only valid if the @e hr http_status is #MHD_HTTP_OK.
*/
- struct TALER_Amount refund_fee;
+ struct TALER_ExchangePublicKeyP exchange_pub;
/**
- * What is the URL of the exchange that issued @a coin_pub?
+ * Signature of the exchange affirming the refund,
+ * only valid if the @e hr http_status is #MHD_HTTP_OK.
*/
- const char *exchange_url;
+ struct TALER_ExchangeSignatureP exchange_sig;
};
/**
- * Pay a merchant. API for frontends talking to backends. Here,
- * the frontend does not have the coin's private keys, but just
- * the public keys and signatures. Note the sublte difference
- * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ * Callback to process a GET /refund request
*
- * @param ctx execution context
- * @param merchant_url base URL of the merchant
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param h_contract_terms hash of the contract terms to which the refund is applied
* @param merchant_pub public key of the merchant
- * @param order_id which order should be paid
- * @param num_coins length of the @a coins array
- * @param coins array of coins to pay with
- * @param pay_cb the callback to call when a reply for this request is available
- * @param pay_cb_cls closure for @a pay_cb
- * @return a handle for this request
+ * @param num_details length of the @a details array
+ * @param details details about the refund processing
*/
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_frontend (
- struct GNUNET_CURL_Context *ctx,
- const char *merchant_url,
+typedef void
+(*TALER_MERCHANT_RefundLookupCallback) (
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct GNUNET_HashCode *h_contract_terms,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const char *order_id,
- unsigned int num_coins,
- const struct TALER_MERCHANT_PaidCoin *coins,
- TALER_MERCHANT_PayCallback pay_cb,
- void *pay_cb_cls);
+ unsigned int num_details,
+ const struct TALER_MERCHANT_RefundDetail *details);
+
+
+/**
+ * Does a GET /refund.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param order_id order id used to perform the lookup
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to the callback
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_RefundLookupOperation *
+TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *order_id,
+ TALER_MERCHANT_RefundLookupCallback cb,
+ void *cb_cls);
+
+/**
+ * Cancel a GET /refund request.
+ *
+ * @param rlo the refund increasing operation to cancel
+ */
+void
+TALER_MERCHANT_refund_lookup_cancel (
+ struct TALER_MERCHANT_RefundLookupOperation *rlo);
+
+
+/**
+ * Handle for a POST /refund operation.
+ */
+struct TALER_MERCHANT_RefundIncreaseOperation;
/**
- * Cancel a /pay request. Note that if you cancel a request like
- * this, you have no assurance that the request has not yet been
- * forwarded to the merchant. Thus, the payment may still succeed or
- * fail. Re-issue the original /pay request to resume/retry and
- * obtain a definitive result, or /refresh the coins involved to
- * ensure that the merchant can no longer complete the payment.
+ * Callback to process a POST /refund request
*
- * @param ph the payment request handle
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec taler-specific error code
+ */
+typedef void
+(*TALER_MERCHANT_RefundIncreaseCallback) (
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Increase the refund associated to a order
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param backend_url backend's base URL, including final "/"
+ * @param order_id id of the order whose refund is to be increased
+ * @param refund amount to which increase the refund
+ * @param reason human-readable reason justifying the refund
+ * @param cb callback processing the response from /refund
+ * @param cb_cls closure for cb
+ */
+struct TALER_MERCHANT_RefundIncreaseOperation *
+TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *order_id,
+ const struct TALER_Amount *refund,
+ const char *reason,
+ TALER_MERCHANT_RefundIncreaseCallback cb,
+ void *cb_cls);
+
+/**
+ * Cancel a POST /refund request.
+ *
+ * @param rio the refund increasing operation to cancel
*/
void
-TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *ph);
+TALER_MERCHANT_refund_increase_cancel (
+ struct TALER_MERCHANT_RefundIncreaseOperation *rio);
+
+
+/* ********************* /proposal *********************** */
+
+
+/* ********************* /pay *********************** */
/* ********************* /track/transfer *********************** */
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index 818e7ddd..2ef75de4 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -299,6 +299,41 @@ typedef void
struct GNUNET_TIME_Absolute timestamp);
+/**
+ * Function called with information about a coin that was deposited.
+ *
+ * @param cls closure
+ * @param coin_pub public key of the coin
+ * @param exchange_url URL of the exchange that issued the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee wire fee the exchange charges
+ */
+typedef void
+(*TALER_MERCHANTDB_DepositCallback)(
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_Amount *wire_fee);
+
+
+/**
+ * Function called with information about a refund.
+ *
+ * @param cls closure
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ */
+typedef void
+(*TALER_MERCHANTDB_RefundCallback)(
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *refund_amount);
+
+
/* **************** OLD: ******************** */
/**
@@ -415,7 +450,7 @@ typedef void
* @param refund_fee cost of this refund operation
*/
typedef void
-(*TALER_MERCHANTDB_RefundCallback)(
+(*TALER_MERCHANTDB_CoinRefundCallback)(
void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const char *exchange_url,
@@ -853,28 +888,91 @@ struct TALER_MERCHANTDB_Plugin
const char *order_id,
struct GNUNET_TIME_Relative legal_expiration);
- /* ****************** OLD API ******************** */
+ /**
+ * Lookup information about coins that were successfully deposited for a
+ * given contract.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup deposits for
+ * @param h_contract_terms proposal data's hashcode
+ * @param cb function to call with payment data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_deposits)(void *cls,
+ const char *instance_id,
+ const struct GNUNET_HashCode *h_contract_terms,
+ TALER_MERCHANTDB_DepositCallback cb,
+ void *cb_cls);
/**
- * Mark contract terms as paid. Needed by /history as only paid
- * contracts must be shown.
+ * Insert payment confirmation from the exchange into the database.
*
- * NOTE: we can't get the list of (paid) contracts from the
- * transactions table because it lacks contract_terms plain JSON.
- * In facts, the protocol doesn't allow to store contract_terms in
- * transactions table, as /pay handler doesn't receive this data
- * (only /proposal does).
+ * @param cls closure
+ * @param instance_id instance to lookup deposits for
+ * @param h_contract_terms proposal data's hashcode
+ * @param coin_pub public key of the coin
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param wire_fee wire fee the exchange charges
+ * @param exchange_pub public key used by the exchange for @a exchange_sig
+ * @param exchange_sig signature from exchange that coin was accepted
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_deposit)(void *cls,
+ const char *instance_id,
+ const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const char *exchange_url,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_Amount *wire_fee,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+ /**
+ * Obtain refunds associated with a contract.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id instance to lookup refunds for
+ * @param h_contract_terms hash code of the contract
+ * @param rc function to call for each coin on which there is a refund
+ * @param rc_cls closure for @a rc
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_refunds)(void *cls,
+ const char *instance_id,
+ const struct GNUNET_HashCode *h_contract_terms,
+ TALER_MERCHANTDB_RefundCallback rc,
+ void *rc_cls);
+
+
+ /**
+ * Mark contract as paid and store the current @a session_id
+ * for which the contract was paid.
*
* @param cls closure
+ * @param instance_id instance to mark contract as paid for
* @param h_contract_terms hash of the contract that is now paid
- * @param merchant_pub merchant's public key
+ * @param session_id the session that paid the contract, can be NULL
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
- (*mark_proposal_paid)(void *cls,
+ (*mark_contract_paid)(void *cls,
+ const char *instance_id,
const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_MerchantPublicKeyP *merchant_pub);
+ const char *session_id);
+
+
+ /* ****************** OLD API ******************** */
+
/**
* Store the order ID that was used to pay for a resource within a session.
@@ -1023,35 +1121,6 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Insert payment confirmation from the exchange into the database.
- *
- * @param cls closure
- * @param h_contract_terms proposal data's hashcode
- * @param merchant_pub merchant's public key
- * @param coin_pub public key of the coin
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param wire_fee wire fee the exchange charges
- * @param signkey_pub public key used by the exchange for @a exchange_proof
- * @param exchange_proof proof from exchange that coin was accepted
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*store_deposit)(void *cls,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const char *exchange_url,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee,
- const struct TALER_ExchangePublicKeyP *signkey_pub,
- const json_t *exchange_proof);
-
-
- /**
* Insert mapping of @a coin_pub and @a h_contract_terms to
* corresponding @a wtid.
*
@@ -1119,25 +1188,6 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Lookup information about coin payments by proposal data's hashcode.
- *
- * @param cls closure
- * @param h_contract_terms proposal data's hashcode
- * @param merchant_pub merchant's public key. It's AND'd with @a h_contract_terms
- * in order to find the result.
- * @param cb function to call with payment data
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*find_payments)(void *cls,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_MERCHANTDB_CoinDepositCallback cb,
- void *cb_cls);
-
-
- /**
* Lookup information about coin payments by h_contract_terms and coin.
*
* @param cls closure
@@ -1271,25 +1321,6 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Obtain refunds associated with a contract.
- *
- * @param cls closure, typically a connection to the db
- * @param merchant_pub public key of the merchant instance
- * @param h_contract_terms hash code of the contract
- * @param rc function to call for each coin on which there is a refund
- * @param rc_cls closure for @a rc
- * @return transaction status
- */
- enum GNUNET_DB_QueryStatus
- (*get_refunds_from_contract_terms_hash)(
- void *cls,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_contract_terms,
- TALER_MERCHANTDB_RefundCallback rc,
- void *rc_cls);
-
-
- /**
* Obtain refund proofs associated with a refund operation on a
* coin.
*