summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-04-30 21:23:40 +0200
committerChristian Grothoff <christian@grothoff.org>2020-04-30 21:23:40 +0200
commit577e344b018cf186e179edf8fa56101659e25307 (patch)
tree5dec709ed9dbdb48346499d64c41d78a447fc48e /src/backend/taler-merchant-httpd_post-orders-ID-abort.c
parent3f5bc6fa636e278d54f8a17c6f4d4b15cc63608f (diff)
downloadmerchant-577e344b018cf186e179edf8fa56101659e25307.tar.gz
merchant-577e344b018cf186e179edf8fa56101659e25307.tar.bz2
merchant-577e344b018cf186e179edf8fa56101659e25307.zip
work on /pay and /abort processing
Diffstat (limited to 'src/backend/taler-merchant-httpd_post-orders-ID-abort.c')
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-abort.c2326
1 files changed, 581 insertions, 1745 deletions
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
index 7a1b7fd8..604490d4 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
@@ -16,31 +16,24 @@
License along with TALER; see the file COPYING. If not,
see <http://www.gnu.org/licenses/>
*/
-
/**
- * @file backend/taler-merchant-httpd_pay.c
- * @brief handling of /pay requests
+ * @file backend/taler-merchant-httpd_post-orders-ID-abort.c
+ * @brief handling of POST /orders/$ID/abort 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"
/**
* How long to wait before giving up processing with the exchange?
*/
-#define PAY_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \
- 30))
+#define ABORT_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \
+ 30))
/**
* How often do we retry the (complex!) database transaction?
@@ -48,26 +41,31 @@
#define MAX_RETRIES 5
/**
- * Information we keep for an individual call to the /pay handler.
+ * Information we keep for an individual call to the /abort handler.
*/
-struct PayContext;
+struct AbortContext;
/**
- * Information kept during a /pay request for each coin.
+ * Information kept during a /abort request for each coin.
*/
-struct DepositConfirmation
+struct RefundDetails
{
/**
- * Reference to the main PayContext
+ * Public key of the coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Reference to the main AbortContext
*/
- struct PayContext *pc;
+ struct AbortContext *ac;
/**
- * Handle to the deposit operation we are performing for
+ * Handle to the refund operation we are performing for
* this coin, NULL after the operation is done.
*/
- struct TALER_EXCHANGE_DepositHandle *dh;
+ struct TALER_EXCHANGE_RefundHandle *rh;
/**
* URL of the exchange that issued this coin.
@@ -75,9 +73,10 @@ struct DepositConfirmation
char *exchange_url;
/**
- * Denomination of this coin.
+ * Body of the response from the exchange. Note that the body returned MUST
+ * be freed (if non-NULL).
*/
- struct TALER_DenominationPublicKey denom;
+ json_t *exchange_reply;
/**
* Amount this coin contributes to the total purchase price.
@@ -86,80 +85,59 @@ struct DepositConfirmation
struct TALER_Amount amount_with_fee;
/**
- * Fee charged by the exchange for the deposit operation of this coin.
- */
- struct TALER_Amount deposit_fee;
-
- /**
* Fee charged by the exchange for the refund operation of this coin.
*/
struct TALER_Amount refund_fee;
/**
- * Wire fee charged by the exchange of this coin.
- */
- struct TALER_Amount wire_fee;
-
- /**
- * Public key of the coin.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Signature using the @e denom key over the @e coin_pub.
- */
- struct TALER_DenominationSignature ub_sig;
-
- /**
- * Signature of the coin's private key over the contract.
- */
- struct TALER_CoinSpendSignatureP coin_sig;
-
- /**
- * Offset of this coin into the `dc` array of all coins in the
- * @e pc.
+ * Offset of this coin into the `rd` array of all coins in the
+ * @e ac.
*/
unsigned int index;
/**
- * #GNUNET_YES if we found this coin in the database.
+ * HTTP status returned by the exchange (if any).
*/
- int found_in_db;
+ unsigned int http_status;
/**
- * #GNUNET_YES if this coin was refunded.
+ * Did we try to process this refund yet?
*/
- int refunded;
+ boolean processed;
};
/**
- * Information we keep for an individual call to the /pay handler.
+ * Information we keep for an individual call to the /abort handler.
*/
-struct PayContext
+struct AbortContext
{
/**
- * This field MUST be first for handle_mhd_completion_callback() to work
- * when it treats this struct as a `struct TM_HandlerContext`.
+ * Hashed contract terms (according to client).
*/
- struct TM_HandlerContext hc;
+ struct GNUNET_HashCode h_contract_terms;
+
+ /**
+ * Context for our operation.
+ */
+ struct TMH_HandlerContext *hc;
/**
* Stored in a DLL.
*/
- struct PayContext *next;
+ struct AbortContext *next;
/**
* Stored in a DLL.
*/
- struct PayContext *prev;
+ struct AbortContext *prev;
/**
* Array with @e coins_cnt coins we are despositing.
*/
- struct DepositConfirmation *dc;
+ struct RefundDetails *rd;
/**
* MHD connection to return to
@@ -167,25 +145,8 @@ struct PayContext
struct MHD_Connection *connection;
/**
- * Instance of the payment's instance (in JSON format)
- */
- struct MerchantInstance *mi;
-
- /**
- * 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;
-
- /**
* Task called when the (suspended) processing for
- * the /pay request times out.
+ * the /abort request times out.
* Happens when we don't get a response from the exchange.
*/
struct GNUNET_SCHEDULER_Task *timeout_task;
@@ -196,7 +157,7 @@ struct PayContext
struct MHD_Response *response;
/**
- * Handle to the exchange that we are doing the payment with.
+ * Handle to the exchange that we are doing the abortment with.
* (initially NULL while @e fo is trying to find a exchange).
*/
struct TALER_EXCHANGE_Handle *mh;
@@ -214,124 +175,7 @@ struct PayContext
const char *current_exchange;
/**
- * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
- */
- void *json_parse_context;
-
- /**
- * Optional session id given in @e root.
- * NULL if not given.
- */
- char *session_id;
-
- /**
- * Transaction ID given in @e root.
- */
- char *order_id;
-
- /**
- * Fulfillment URL from @e contract_terms.
- */
- char *fulfillment_url;
-
- /**
- * Hashed proposal.
- */
- struct GNUNET_HashCode h_contract_terms;
-
- /**
- * "h_wire" from @e contract_terms. Used to identify
- * the instance's wire transfer method.
- */
- struct GNUNET_HashCode h_wire;
-
- /**
- * Maximum fee the merchant is willing to pay, from @e root.
- * Note that IF the total fee of the exchange is higher, that is
- * acceptable to the merchant if the customer is willing to
- * pay the difference
- * (i.e. amount - max_fee <= actual-amount - actual-fee).
- */
- struct TALER_Amount max_fee;
-
- /**
- * Maximum wire fee the merchant is willing to pay, from @e root.
- * Note that IF the total fee of the exchange is higher, that is
- * acceptable to the merchant if the customer is willing to
- * pay the amorized difference. Wire fees are charged over an
- * aggregate of several translations, hence unlike the deposit
- * fees, they are amortized over several customer's transactions.
- * The contract specifies under @e wire_fee_amortization how many
- * customer's transactions he expects the wire fees to be amortized
- * over on average. Thus, if the wire fees are larger than
- * @e max_wire_fee, each customer is expected to contribute
- * $\frac{actual-wire-fee - max_wire_fee}{wire_fee_amortization}$.
- * The customer's contribution may be further reduced by the
- * difference between @e max_fee and the sum of the deposit fees.
- *
- * Default is that the merchant is unwilling to pay any wire fees.
- */
- struct TALER_Amount max_wire_fee;
-
- /**
- * Amount from @e root. This is the amount the merchant expects
- * to make, minus @e max_fee.
- */
- struct TALER_Amount amount;
-
- /**
- * Considering all the coins with the "found_in_db" flag
- * set, what is the total amount we were so far paid on
- * this contract?
- */
- struct TALER_Amount total_paid;
-
- /**
- * Considering all the coins with the "found_in_db" flag
- * set, what is the total amount we had to pay in deposit
- * fees so far on this contract?
- */
- struct TALER_Amount total_fees_paid;
-
- /**
- * Considering all the coins with the "found_in_db" flag
- * set, what is the total amount we already refunded?
- */
- struct TALER_Amount total_refunded;
-
- /**
- * Wire transfer deadline. How soon would the merchant like the
- * wire transfer to be executed?
- */
- struct GNUNET_TIME_Absolute wire_transfer_deadline;
-
- /**
- * Timestamp from @e contract_terms.
- */
- struct GNUNET_TIME_Absolute timestamp;
-
- /**
- * Refund deadline from @e contract_terms.
- */
- struct GNUNET_TIME_Absolute refund_deadline;
-
- /**
- * Deadline for the customer to pay for this proposal.
- */
- struct GNUNET_TIME_Absolute pay_deadline;
-
- /**
- * Number of transactions that the wire fees are expected to be
- * amortized over. Never zero, defaults (conservateively) to 1.
- * May be higher if merchants expect many small transactions to
- * be aggregated and thus wire fees to be reasonably amortized
- * due to aggregation.
- */
- uint32_t wire_fee_amortization;
-
- /**
- * Number of coins this payment is made of. Length
- * of the @e dc array.
+ * Number of coins this abort is for. Length of the @e rd array.
*/
unsigned int coins_cnt;
@@ -366,537 +210,220 @@ struct PayContext
* #GNUNET_NO if the @e connection was not suspended,
* #GNUNET_YES if the @e connection was suspended,
* #GNUNET_SYSERR if @e connection was resumed to as
- * part of #MH_force_pc_resume during shutdown.
+ * part of #MH_force_ac_resume during shutdown.
*/
int suspended;
- /**
- * #GNUNET_YES if we already tried a forced /keys download.
- */
- int tried_force_keys;
-
- /**
- * Which operational mode is the /pay request made in?
- */
- enum { PC_MODE_PAY, PC_MODE_ABORT_REFUND } mode;
-
};
/**
- * Head of active pay context DLL.
+ * Head of active abort context DLL.
*/
-static struct PayContext *pc_head;
+static struct AbortContext *ac_head;
/**
- * Tail of active pay context DLL.
+ * Tail of active abort context DLL.
*/
-static struct PayContext *pc_tail;
+static struct AbortContext *ac_tail;
/**
* Abort all pending /deposit operations.
*
- * @param pc pay context to abort
+ * @param ac abort context to abort
*/
static void
-abort_deposit (struct PayContext *pc)
+abort_refunds (struct AbortContext *ac)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Aborting pending /deposit operations\n");
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (unsigned int i = 0; i<ac->coins_cnt; i++)
{
- struct DepositConfirmation *dci = &pc->dc[i];
+ struct RefundDetails *rdi = &ac->rd[i];
- if (NULL != dci->dh)
+ if (NULL != rdi->dh)
{
- TALER_EXCHANGE_deposit_cancel (dci->dh);
- dci->dh = NULL;
+ TALER_EXCHANGE_refund_cancel (rdi->rh);
+ rdi->rh = NULL;
}
}
}
/**
- * Force all pay contexts to be resumed as we are about
+ * Force all abort contexts to be resumed as we are about
* to shut down MHD.
*/
void
-MH_force_pc_resume ()
+TMH_force_ac_resume ()
{
- for (struct PayContext *pc = pc_head;
- NULL != pc;
- pc = pc->next)
+ for (struct AbortContext *ac = ac_head;
+ NULL != ac;
+ ac = ac->next)
{
- abort_deposit (pc);
- if (NULL != pc->timeout_task)
+ abort_deposit (ac);
+ if (NULL != ac->timeout_task)
{
- GNUNET_SCHEDULER_cancel (pc->timeout_task);
- pc->timeout_task = NULL;
+ GNUNET_SCHEDULER_cancel (ac->timeout_task);
+ ac->timeout_task = NULL;
}
- if (GNUNET_YES == pc->suspended)
+ if (GNUNET_YES == ac->suspended)
{
- pc->suspended = GNUNET_SYSERR;
- MHD_resume_connection (pc->connection);
+ ac->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (ac->connection);
}
}
}
/**
- * Resume the given pay context and send the given response.
- * Stores the response in the @a pc and signals MHD to resume
+ * Resume the given abort context and send the given response.
+ * Stores the response in the @a ac and signals MHD to resume
* the connection. Also ensures MHD runs immediately.
*
- * @param pc payment context
+ * @param ac abortment context
* @param response_code response code to use
* @param response response data to send back
*/
static void
-resume_pay_with_response (struct PayContext *pc,
- unsigned int response_code,
- struct MHD_Response *response)
+resume_abort_with_response (struct AbortContext *ac,
+ unsigned int response_code,
+ struct MHD_Response *response)
{
- pc->response_code = response_code;
- pc->response = response;
+ abort_refunds (ac);
+ ac->response_code = response_code;
+ ac->response = response;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Resuming /pay handling as exchange interaction is done (%u)\n",
+ "Resuming /abort handling as exchange interaction is done (%u)\n",
response_code);
- if (NULL != pc->timeout_task)
+ if (NULL != ac->timeout_task)
{
- GNUNET_SCHEDULER_cancel (pc->timeout_task);
- pc->timeout_task = NULL;
+ GNUNET_SCHEDULER_cancel (ac->timeout_task);
+ ac->timeout_task = NULL;
}
- GNUNET_assert (GNUNET_YES == pc->suspended);
- pc->suspended = GNUNET_NO;
- MHD_resume_connection (pc->connection);
+ GNUNET_assert (GNUNET_YES == ac->suspended);
+ ac->suspended = GNUNET_NO;
+ MHD_resume_connection (ac->connection);
TMH_trigger_daemon (); /* we resumed, kick MHD */
}
/**
- * Resume payment processing with an error.
+ * Resume abortment processing with an error.
*
- * @param pc operation to resume
+ * @param ac operation to resume
* @param http_status http status code to return
* @param ec taler error code to return
* @param msg human readable error message
*/
static void
-resume_pay_with_error (struct PayContext *pc,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const char *msg)
+resume_abort_with_error (struct AbortContext *ac,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *msg)
{
- resume_pay_with_response (pc,
- http_status,
- TALER_MHD_make_error (ec,
- msg));
+ resume_abort_with_response (ac,
+ http_status,
+ TALER_MHD_make_error (ec,
+ msg));
}
/**
- * Generate a response that indicates payment success.
+ * Generate a response that indicates abortment success.
*
- * @param pc payment context
+ * @param ac abortment context
*/
static void
-generate_success_response (struct PayContext *pc)
+generate_success_response (struct AbortContext *ac)
{
json_t *refunds;
- struct GNUNET_CRYPTO_EddsaSignature sig;
- /* Check for applicable refunds */
+ refunds = json_array ();
+ if (NULL == 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,
+ GNUNET_break (0);
+ resume_abort_with_error (ac,
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);
+ TALER_EC_JSON_ALLOCATION_FAILURE,
+ "could not create JSON array");
+ return;
}
-
- /* Build the response */
+ for (unsigned int i = 0; i<ac->coins_cnt; i++)
{
- 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)
+ struct RefundDetails *rdi = &ac->rd[i];
+
+ if (0 !=
+ json_array_append_new (
+ refunds,
+ json_pack ("{s:I, s:O, s:o s:o s:o}",
+ "exchange_status",
+ (json_int_t) rdi->exchange_status,
+ "exchange_body",
+ rdi->exchange_body)))
{
+ json_decref (refunds);
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "could not build final response");
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_JSON_ALLOCATION_FAILURE,
+ "could not create JSON array");
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.
- */
-static void
-pay_context_cleanup (struct TM_HandlerContext *hc)
-{
- struct PayContext *pc = (struct PayContext *) hc;
-
- 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);
- dc->ub_sig.rsa_signature = NULL;
- }
- GNUNET_free_non_null (dc->exchange_url);
- }
- GNUNET_free_non_null (pc->dc);
- if (NULL != pc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (pc->fo);
- pc->fo = NULL;
- }
- if (NULL != pc->response)
- {
- 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);
- GNUNET_free (pc);
+ /* Resume and send back the response. */
+ resume_abort_with_response (ac,
+ MHD_HTTP_OK,
+ TALER_MHD_make_json_pack ("{s:o}",
+ "refunds",
+ refunds));
}
/**
- * Check whether the amount paid is sufficient to cover
- * the contract.
+ * Custom cleanup routine for a `struct AbortContext`.
*
- * @param pc payment context to check
- * @return #GNUNET_OK if the payment is sufficient, #GNUNET_SYSERR if it is
- * insufficient
+ * @param hc the `struct AbortContext` to clean up.
*/
-static int
-check_payment_sufficient (struct PayContext *pc)
+static void
+abort_context_cleanup (struct TM_HandlerContext *hc)
{
- 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;
+ struct AbortContext *ac = (struct AbortContext *) hc;
- 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))
+ if (NULL != ac->timeout_task)
{
- 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;
+ GNUNET_SCHEDULER_cancel (ac->timeout_task);
+ ac->timeout_task = NULL;
}
-
- switch (TALER_amount_subtract (&wire_fee_delta,
- &total_wire_fee,
- &pc->max_wire_fee))
+ abort_refunds (ac);
+ for (unsigned int i = 0; i<ac->coins_cnt; i++)
{
- 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);
- }
+ struct RefundDetails *rd = &ac->rd[i];
- /* 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))
+ if (NULL != rdi->exchange_reply)
{
- 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;
+ json_decref (rdi->exchange_reply);
+ rdi->reply = NULL;
}
+ GNUNET_free_non_null (rd->exchange_url);
}
- 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_free_non_null (ac->rd);
+ if (NULL != ac->fo)
{
- 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;
+ TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+ ac->fo = NULL;
}
-
- if (-1 == TALER_amount_cmp (&final_amount,
- &total_needed))
+ if (NULL != ac->response)
{
- /* 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;
+ MHD_destroy_response (ac->response);
+ ac->response = NULL;
}
-
-
- return GNUNET_OK;
+ GNUNET_CONTAINER_DLL_remove (ac_head,
+ ac_tail,
+ ac);
+ GNUNET_free (ac);
}
@@ -904,196 +431,61 @@ check_payment_sufficient (struct PayContext *pc)
* Find the exchange we need to talk to for the next
* pending deposit permission.
*
- * @param pc payment context we are processing
+ * @param ac abortment context we are processing
*/
static void
-find_next_exchange (struct PayContext *pc);
+find_next_exchange (struct AbortContext *ac);
/**
- * Begin of the DB transaction. If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Function called with the result from the exchange (to be
+ * passed back to the wallet).
*
- * @param pc payment context to transact
- */
-static void
-begin_transaction (struct PayContext *pc);
-
-
-/**
- * Callback to handle a deposit permission's response.
- *
- * @param cls a `struct DepositConfirmation` (i.e. a pointer
- * into the global array of confirmations and an index for this call
- * in that array). That way, the last executed callback can detect
- * that no other confirmations are on the way, and can pack a response
- * 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 cls closure
+ * @param hr HTTP response data
+ * @param sign_key exchange key used to sign @a obj, or NULL
+ * @param signature the actual signature, or NULL on error
*/
static void
-deposit_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_ExchangePublicKeyP *sign_key)
+refund_cb (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ const struct TALER_ExchangePublicKeyP *sign_key,
+ const struct TALER_ExchangeSignatureP *signature)
{
- struct DepositConfirmation *dc = cls;
- struct PayContext *pc = dc->pc;
- enum GNUNET_DB_QueryStatus qs;
-
- dc->dh = NULL;
- GNUNET_assert (GNUNET_YES == pc->suspended);
- pc->pending_at_ce--;
- if (MHD_HTTP_OK != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Deposit operation failed with HTTP code %u/%d\n",
- hr->http_status,
- (int) hr->ec);
- /* Transaction failed; stop all other ongoing deposits */
- abort_deposit (pc);
-
- if (5 == hr->http_status / 100)
- {
- /* internal server error at exchange */
- resume_pay_with_response (pc,
- MHD_HTTP_SERVICE_UNAVAILABLE,
- TALER_MHD_make_json_pack (
- "{s:s, s:I, s:I, s:I}",
- "hint",
- "exchange had an internal server error",
- "code",
- (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
- "exchange_code",
- (json_int_t) hr->ec,
- "exchange_http_status",
- (json_int_t) hr->http_status));
- }
- else if (NULL == hr->reply)
- {
- /* We can't do anything meaningful here, the exchange did something wrong */
- resume_pay_with_response (pc,
- MHD_HTTP_FAILED_DEPENDENCY,
- TALER_MHD_make_json_pack (
- "{s:s, s:I, s:I, s:I}",
- "hint",
- "exchange failed, response body not even in JSON",
- "code",
- (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
- "exchange_code",
- (json_int_t) hr->ec,
- "exchange_http_status",
- (json_int_t) hr->http_status));
- }
- else
- {
- /* Forward error, adding the "coin_pub" for which the
- error was being generated */
- if (TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS == hr->ec)
- resume_pay_with_response (
- pc,
- MHD_HTTP_CONFLICT,
- TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
- "hint",
- "exchange failed on deposit of a coin",
- "code",
- (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
- "exchange_code",
- (json_int_t) hr->ec,
- "exchange_http_status",
- (json_int_t) hr->http_status,
- "coin_pub",
- GNUNET_JSON_from_data_auto (&dc->coin_pub),
- "exchange_reply",
- hr->reply));
- else
- resume_pay_with_response (
- pc,
- MHD_HTTP_FAILED_DEPENDENCY,
- TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
- "hint",
- "exchange failed on deposit of a coin",
- "code",
- (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
- "exchange_code",
- (json_int_t) hr->ec,
- "exchange_http_status",
- (json_int_t) hr->http_status,
- "coin_pub",
- GNUNET_JSON_from_data_auto (&dc->coin_pub),
- "exchange_reply",
- hr->reply));
- }
- return;
- }
- /* store result to DB */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing successful payment for h_contract_terms `%s' and merchant `%s'\n",
- GNUNET_h2s (&pc->h_contract_terms),
- TALER_B2S (&pc->mi->pubkey));
- /* 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);
- if (0 > qs)
- {
- /* Special report if retries insufficient */
- abort_deposit (pc);
- 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);
- /* Forward error including 'proof' for the body */
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_STORE_PAY_ERROR,
- "Merchant database error");
- return;
- }
- dc->found_in_db = GNUNET_YES;
- pc->pending--;
-
- if (0 != pc->pending_at_ce)
- return; /* still more to do with current exchange */
- find_next_exchange (pc);
+ struct RefundDetails *rd = cls;
+ struct AbortContext *ac = rd->ac;
+
+ (void) sign_key;
+ (void) signature;
+ rd->rh = NULL;
+ rd.http_status = hr->status;
+ rd.exchange_reply = json_incref ((json_t*) hr->reply);
+ ac->pending_at_ce--;
+ if (0 == ac->pending_at_ce)
+ find_next_exchange (ac);
}
/**
* Function called with the result of our exchange lookup.
*
- * @param cls the `struct PayContext`
+ * @param cls the `struct AbortContext`
* @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
*/
static void
-process_pay_with_exchange (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *mh,
- const struct TALER_Amount *wire_fee,
- int exchange_trusted)
+process_abort_with_exchange (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ struct TALER_EXCHANGE_Handle *exchange_handle,
+ const struct TALER_Amount *wire_fee,
+ int exchange_trusted)
{
- struct PayContext *pc = cls;
- const struct TALER_EXCHANGE_Keys *keys;
+ struct AbortContext *ac = cls;
+ struct TMH_HandlerContext *hc = pc->hc;
pc->fo = NULL;
GNUNET_assert (GNUNET_YES == pc->suspended);
@@ -1111,7 +503,7 @@ process_pay_with_exchange (void *cls,
"hint",
"failed to obtain meta-data from exchange",
"code",
- (json_int_t) TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
+ (json_int_t) TALER_EC_ABORT_EXCHANGE_KEYS_FAILURE,
"exchange_http_status",
(json_int_t) hr->http_status,
"exchange_code",
@@ -1120,206 +512,93 @@ process_pay_with_exchange (void *cls,
hr->reply));
return;
}
- pc->mh = mh;
- keys = TALER_EXCHANGE_get_keys (mh);
- if (NULL == keys)
- {
- GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK */
- resume_pay_with_error (pc,
- MHD_HTTP_FAILED_DEPENDENCY,
- TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
- "no keys");
- 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
+ /* Initiate refund operation for all coins of
the current exchange (!) */
- GNUNET_assert (0 == pc->pending_at_ce);
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ GNUNET_assert (0 == ac->pending_at_ce);
+ for (unsigned int i = 0; i<ac->coins_cnt; i++)
{
- struct DepositConfirmation *dc = &pc->dc[i];
- const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
- enum TALER_ErrorCode ec;
- unsigned int hc;
-
- 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)
+ struct RefundDetails *rdi = &ac->rd[i];
+
+ if (rdi->processed)
continue;
- if (0 != strcmp (dc->exchange_url,
- pc->current_exchange))
+ GNUNET_assert (NULL == rdi->rh);
+ if (0 != strcmp (rdi->exchange_url,
+ ac->current_exchange))
continue;
- denom_details = TALER_EXCHANGE_get_denomination_key (keys,
- &dc->denom);
- if (NULL == denom_details)
+ rdi->processed = true;
+ ac->pending--;
+ rdi->rh = TALER_EXCHANGE_refund (exchange_handle,
+ &rdi->amount_with_fee,
+ &rdi->refund_fee,
+ &ac->h_contract_terms,
+ &rdi->coin_pub,
+ 0, /* rtransaction_id */
+ &ac->hc->instance->merchant_priv,
+ &refund_Cb,
+ rdi);
+ if (NULL == rdi->rh)
{
- struct GNUNET_HashCode h_denom;
-
- if (! pc->tried_force_keys)
- {
- /* let's try *forcing* a re-download of /keys from the exchange.
- Maybe the wallet has seen /keys that we missed. */
- pc->tried_force_keys = GNUNET_YES;
- pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
- pc->wm->wire_method,
- GNUNET_YES,
- &process_pay_with_exchange,
- pc);
- if (NULL != pc->fo)
- 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,
- TALER_MHD_make_json_pack (
- "{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)));
- return;
- }
- if (GNUNET_OK !=
- TMH_AUDITORS_check_dk (mh,
- denom_details,
- exchange_trusted,
- &hc,
- &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)));
- 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,
- &dc->amount_with_fee,
- pc->wire_transfer_deadline,
- pc->wm->j_wire,
- &pc->h_contract_terms,
- &dc->coin_pub,
- &dc->ub_sig,
- &dc->denom,
- pc->timestamp,
- &pc->mi->pubkey,
- pc->refund_deadline,
- &dc->coin_sig,
- &deposit_cb,
- dc);
- if (NULL == dc->dh)
- {
- /* Signature was invalid or some other constraint was not satisfied. If
- the exchange was unavailable, we'd get that information in the
- callback. */
GNUNET_break_op (0);
- resume_pay_with_response (
- pc,
- MHD_HTTP_UNAUTHORIZED,
- TALER_MHD_make_json_pack (
- "{s:s, s:I, s:i}",
- "hint", "deposit signature invalid",
- "code", (json_int_t) TALER_EC_PAY_COIN_SIGNATURE_INVALID,
- "coin_idx", i));
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_EXCHANGE_REFUND_FAILED,
+ "Failed to start refund with exchange");
return;
}
- if (TMH_force_audit)
- TALER_EXCHANGE_deposit_force_dc (dc->dh);
pc->pending_at_ce++;
}
}
/**
+ * Begin of the DB transaction. If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param ac abortment context to transact
+ */
+static void
+begin_transaction (struct AbortContext *ac);
+
+
+/**
* Find the exchange we need to talk to for the next
* pending deposit permission.
*
- * @param pc payment context we are processing
+ * @param ac abortment context we are processing
*/
static void
-find_next_exchange (struct PayContext *pc)
+find_next_exchange (struct AbortContext *ac)
{
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (unsigned int i = 0; i<ac->coins_cnt; i++)
{
- struct DepositConfirmation *dc = &pc->dc[i];
+ struct RefundDetails *rdi = &ac->rd[i];
- if (GNUNET_YES != dc->found_in_db)
+ if (! rdi->processed)
{
- db->preflight (db->cls);
- pc->current_exchange = dc->exchange_url;
- pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
- pc->wm->wire_method,
+ ac->current_exchange = rd->exchange_url;
+ ac->fo = TMH_EXCHANGES_find_exchange (ac->current_exchange,
+ ac->wm->wire_method,
GNUNET_NO,
- &process_pay_with_exchange,
- pc);
- if (NULL == pc->fo)
+ &process_abort_with_exchange,
+ ac);
+ if (NULL == ac->fo)
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_EXCHANGE_LOOKUP_FAILED,
- "Failed to lookup exchange by URL");
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_EXCHANGE_LOOKUP_FAILED,
+ "Failed to lookup exchange by URL");
return;
}
return;
}
}
- pc->current_exchange = NULL;
- db->preflight (db->cls);
+ ac->current_exchange = NULL;
+ GNUNET_assert (0 == ac->pending);
/* We are done with all the HTTP requests, go back and try
the 'big' database transaction! (It should work now!) */
- 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");
+ begin_transaction (ac);
}
@@ -1337,919 +616,476 @@ handle_pay_timeout (void *cls)
* @param exchange_proof proof from exchange that coin was accepted
*/
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)
-{
- 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 */
-
- /* 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;
- 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;
- }
- 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;
- pc->pending--;
- }
-}
-
-
-/**
- * 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 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)
- */
-static enum GNUNET_GenericReturnValue
-parse_pay (struct MHD_Connection *connection,
- const json_t *root,
- struct PayContext *pc)
+refund_coins (void *cls,
+ 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)
{
- 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;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- return res;
- }
-
- 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;
- }
-
- {
- 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;
- }
-
- 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));
+ struct AbortContext *ac = cls;
- if (NULL == json_object_get (pc->contract_terms,
- "merchant"))
+ for (unsigned int i = 0; i<ac->coins_cnt; i++)
{
- /* 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;
- {
- 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 RefundDetails *rdi = &ac->rd[i];
- res = TALER_MHD_parse_json_data (connection,
- pc->contract_terms,
- espec);
- if (GNUNET_YES != res)
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
+ if ( (0 !=
+ GNUNET_memcmp (coin_pub,
+ &rdi->coin_pub)) ||
+ (0 !=
+ strcmp (exchange_url,
+ rdi->exchange_url)) )
+ continue; /* not in request */
- pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
- if (pc->wire_transfer_deadline.abs_value_us <
- pc->refund_deadline.abs_value_us)
- {
- /* 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");
- }
-
- if (pc->pay_deadline.abs_value_us <
- GNUNET_TIME_absolute_get ().abs_value_us)
- {
- /* 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;
-
- 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");
- }
- 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 ()
- };
-
- 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;
- }
- }
- else
- {
- /* default is we cover no fee */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (pc->max_fee.currency,
- &pc->max_wire_fee));
- }
-
- if (NULL != json_object_get (pc->contract_terms,
- "wire_fee_amortization"))
- {
- struct GNUNET_JSON_Specification espec[] = {
- GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
- &pc->wire_fee_amortization),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (connection,
- pc->contract_terms,
- espec);
- if ( (GNUNET_YES != res) ||
- (0 == pc->wire_fee_amortization) )
- {
- GNUNET_break_op (0); /* invalid input, use default */
- /* default is no amortization */
- pc->wire_fee_amortization = 1;
- }
- }
- else
- {
- pc->wire_fee_amortization = 1;
- }
-
- pc->coins_cnt = json_array_size (coins);
- if (0 == pc->coins_cnt)
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PAY_COINS_ARRAY_EMPTY,
- "coins");
- }
- /* 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)
+ /* Store refund in DB */
+ qs = TMH_db->refund_coin (TMH_db->cls,
+ ac->hc->instance->settings.id,
+ &ac->h_contract_terms,
+ coin_pub,
+ /* justification */
+ "incomplete abortment aborted");
+ if (0 > qs)
{
- 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)
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- GNUNET_JSON_parse_free (spec);
- GNUNET_break_op (0);
- return res;
+ begin_transaction (ac);
+ return;
}
- dc->exchange_url = GNUNET_strdup (exchange_url);
- dc->index = coins_index;
- dc->pc = pc;
- }
- }
- 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))
- {
- dc->refunded = GNUNET_YES;
- GNUNET_assert (0 <=
- TALER_amount_add (&pc->total_refunded,
- &pc->total_refunded,
- refund_amount));
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+ "Merchant database error storing abort-refund");
+ return;
}
- }
+ } /* for all coins */
}
/**
- * Begin of the DB transaction. If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Begin of the DB transaction. If required (from soft/serialization errors),
+ * the transaction can be restarted here.
*
- * @param pc payment context to transact
+ * @param ac abortment context to transact
*/
static void
-begin_transaction (struct PayContext *pc)
+begin_transaction (struct AbortContext *ac)
{
enum GNUNET_DB_QueryStatus qs;
/* Avoid re-trying transactions on soft errors forever! */
- if (pc->retry_counter++ > MAX_RETRIES)
+ if (ac->retry_counter++ > MAX_RETRIES)
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
- "Soft merchant database error: retry counter exceeded");
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_DB_STORE_TRANSACTION_ERROR,
+ "Soft merchant database error: retry counter exceeded");
return;
}
- GNUNET_assert (GNUNET_YES == pc->suspended);
-
- /* Init. some price accumulators. */
- GNUNET_break (GNUNET_OK ==
- TALER_amount_get_zero (pc->amount.currency,
- &pc->total_paid));
- GNUNET_break (GNUNET_OK ==
- TALER_amount_get_zero (pc->amount.currency,
- &pc->total_fees_paid));
- GNUNET_break (GNUNET_OK ==
- TALER_amount_get_zero (pc->amount.currency,
- &pc->total_refunded));
+ GNUNET_assert (GNUNET_YES == ac->suspended);
/* 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 abort"))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
- "Merchant database error (could not begin transaction)");
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error (could not begin transaction)");
return;
}
- /* 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);
+ /* check payment was indeed incomplete */
+ qs = TMH_db->lookup_paid_order (TMH_db->cls,
+ ac->hc->instance->settings.id,
+ &ac->h_contract_terms,
+ NULL);
+ if (0 < qs)
+ {
+ /* Payment is complete, refuse to abort. */
+ json_decref (terms);
+ TMH_db->rollback (TMH_db->cls);
+ resume_abort_with_error (ac,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ABORT_ABORT_REFUND_REFUSED_ABORTMENT_COMPLETE,
+ "Payment was complete, refusing to abort");
+ return;
+ }
if (0 > qs)
{
- db->rollback (db->cls);
+ TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- begin_transaction (pc);
+ begin_transaction (ac);
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_FETCH_TRANSACTION_ERROR,
- "Merchant database error");
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+ "Merchant database error");
return;
}
- /* 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);
+ /* Mark all deposits we have in our database for the order as refunded. */
+ qs = TMH_db->lookup_deposits (TMH_db->cls,
+ ac->hc->instance->settings.id,
+ &ac->h_contract_terms,
+ &refund_coins,
+ ac);
if (0 > qs)
{
- db->rollback (db->cls);
+ TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- begin_transaction (pc);
+ begin_transaction (ac);
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_FETCH_TRANSACTION_ERROR,
- "Merchant database error checking for refunds");
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error");
return;
}
- /* All the coins known to the database have
- * been processed, now delve into specific case
- * (pay vs. abort) */
-
- if (PC_MODE_ABORT_REFUND == pc->mode)
+ qs = TMH_db->commit (TMH_db->cls);
+ if (0 > qs)
{
- json_t *terms;
-
- /* The wallet is going for a refund,
- (on aborted operation)! */
-
- /* 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)
+ 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 (ac);
return;
}
+ resume_abort_with_error (ac,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+ "Merchant database error: could not commit");
+ 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)
+ /* At this point, the refund got correctly committed
+ into the database. Tell exchange about abort/refund. */
+ if (ac->pending > 0)
+ {
+ find_next_exchange (ac);
+ return;
+ }
+ generate_success_response (ac);
+}
+
+
+/**
+ * Try to parse the abort request into the given abort context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param connection HTTP connection we are receiving abortment on
+ * @param root JSON upload with abortment data
+ * @param ac context we use to handle the abortment
+ * @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_abort (struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc,
+ struct AbortContext *ac)
+{
+ {
+ json_t *coins;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("coins",
+ &coins),
+ GNUNET_JSON_spec_fixed_auto ("h_contract",
+ &ac->h_contract_terms),
+
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_YES != res)
{
- 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");
- return;
+ GNUNET_break_op (0);
+ return res;
}
- qs = db->commit (db->cls);
- if (0 > qs)
+ ac->coins_cnt = json_array_size (coins);
+ if (0 == ac->coins_cnt)
{
- 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;
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ABORT_COINS_ARRAY_EMPTY,
+ "coins");
}
- /* At this point, the refund got correctly committed
- * into the database. */
+ /* note: 1 coin = 1 deposit confirmation expected */
+ ac->pending = ac->coins_cnt;
+ ac->rd = GNUNET_new_array (ac->coins_cnt,
+ struct RefundDetails);
+ /* This loop populates the array 'rd' in 'ac' */
{
- json_t *refunds;
-
- refunds = json_array ();
- if (NULL == refunds)
+ unsigned int coins_index;
+ json_t *coin;
+ json_array_foreach (coins, coins_index, coin)
{
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_JSON_ALLOCATION_FAILURE,
- "could not create JSON array");
- return;
- }
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
- {
- 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 RefundDetails *rd = &ac->rd[coins_index];
+ const char *exchange_url;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_amount ("contribution",
+ &rd->amount_with_fee),
+ TALER_JSON_spec_amount ("refund_fee",
+ &rd->refund_fee),
+ GNUNET_JSON_spec_string ("exchange_url",
+ &exchange_url),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rd->coin_pub),
+ 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;
}
+ rd->exchange_url = GNUNET_strdup (exchange_url);
+ rd->index = coins_index;
+ rd->ac = ac;
}
-
- /* 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)));
}
- return;
- } /* End of PC_MODE_ABORT_REFUND */
-
- /* Default PC_MODE_PAY mode */
+ GNUNET_JSON_parse_free (spec);
+ }
- /* Final termination case: all coins already known, just
- generate ultimate outcome. */
- if (0 == pc->pending)
+ /* Check request against contract on file */
{
- if (GNUNET_OK != check_payment_sufficient (pc))
+ enum GNUNET_DB_QueryStatus qs;
+ json_t *contract_terms;
+
+ qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+ hc->instance->settings.id,
+ hc->infix,
+ &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_ABORT_DB_FETCH_ABORT_ERROR,
+ "Failed to obtain contract terms from DB"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
- /* Payment succeeded, save in database */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Contract `%s' was fully paid\n",
- GNUNET_h2s (&pc->h_contract_terms));
- qs = db->mark_proposal_paid (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey);
- if (qs < 0)
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- 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_PAYMENTS_ERROR,
- "Merchant database error: could not mark proposal as 'paid'");
- return;
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ABORT_PROPOSAL_NOT_FOUND,
+ "Order not found"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
- if ( (NULL != pc->session_id) &&
- (NULL != pc->fulfillment_url) )
+ /* check client provided the right hash and is thus authorized to request aborting */
{
- qs = db->insert_session_info (db->cls,
- pc->session_id,
- pc->fulfillment_url,
- pc->order_id,
- &pc->mi->pubkey);
+ struct GNUNET_HashCode h_contract_terms;
+
+ if (GNUNET_OK !=
+ TALER_JSON_hash (ac->contract_terms,
+ &h_contract_terms))
+ {
+ GNUNET_break (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ABORT_FAILED_COMPUTE_PROPOSAL_HASH,
+ "Failed to hash contract terms"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (0 !=
+ GNUNET_memcmp (&ac->contract_terms,
+ &h_contract_terms))
+ {
+ GNUNET_break_op (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ABORT_CONTRACT_HASH_MISSMATCH,
+ "Provided hash does not match order on file"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling /abort for order `%s' with contract hash `%s'\n",
+ order_id,
+ GNUNET_h2s (&ac->h_contract_terms));
- /* Now commit! */
- if (0 <= qs)
- qs = db->commit (db->cls);
- else
- db->rollback (db->cls);
- if (0 > qs)
+ /* Check abort is still possible */
{
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Absolute wire_transfer_deadline;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_JSON_Specification espec[] = {
+ GNUNET_JSON_spec_absolute_time ("refund_deadline",
+ &refund_deadline),
+ GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
+ &wire_transfer_deadline),
+ GNUNET_JSON_spec_end ()
+ };
+
+ /* 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,
+ ac->contract_terms,
+ espec);
+ if (GNUNET_YES != res)
{
- begin_transaction (pc);
- return;
+ GNUNET_break (0);
+ return res;
+ }
+ now = GNUNET_TIME_absolute_get ();
+ if ( (now.abs_value_us < refund_deadline.abs_value_us) &&
+ ( (0 != refund_deadline.abs_value_us) ||
+ (now.abs_value_us < wire_transfer_deadline.abs_value_us) ) )
+ {
+ /* it is most definitively too late for an abort */
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_ABORT_TOO_LATE,
+ "It is too late to abort the payment"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
}
- 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;
}
- TMH_long_poll_resume (pc->order_id,
- &pc->mi->pubkey,
- NULL);
- generate_success_response (pc);
- return;
}
-
-
- /* we made no DB changes,
- so we can just rollback */
- db->rollback (db->cls);
-
- /* 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 abort 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 AbortContext`
*/
-static MHD_RESULT
-handler_pay_json (struct MHD_Connection *connection,
- const json_t *root,
- struct PayContext *pc)
+static void
+handle_abort_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 AbortContext *ac = cls;
- /* Payment not finished, suspend while we interact with the exchange */
- MHD_suspend_connection (connection);
- pc->suspended = GNUNET_YES;
+ ac->timeout_task = NULL;
+ GNUNET_assert (GNUNET_YES == ac->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 abort with error after timeout\n");
+ if (NULL != ac->fo)
+ {
+ TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+ ac->fo = NULL;
+ }
+ resume_abort_with_error (ac,
+ MHD_HTTP_REQUEST_TIMEOUT,
+ TALER_EC_ABORT_EXCHANGE_TIMEOUT,
+ "likely the exchange did not reply quickly enough");
}
/**
- * Process a payment for a proposal. Takes data from the given MHD
- * connection.
+ * Abort 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)
+TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
{
- struct PayContext *pc;
- enum GNUNET_GenericReturnValue res;
- MHD_RESULT ret;
- json_t *root;
+ struct AbortContext *ac = hc->ctx;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "In handler for /pay.\n");
if (NULL == *connection_cls)
{
- 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;
+ ac = GNUNET_new (struct AbortContext);
+ GNUNET_CONTAINER_DLL_insert (ac_head,
+ ac_tail,
+ ac);
+ ac->connection = connection;
+ ac->hc = hc;
+ hc->ctx = ac;
+ hc->cc = &abort_context_cleanup;
}
- if (GNUNET_SYSERR == pc->suspended)
+ if (GNUNET_SYSERR == ac->suspended)
return MHD_NO; /* during shutdown, we don't generate any more replies */
- if (0 != pc->response_code)
+ if (0 != ac->response_code)
{
+ MHD_RESULT res;
+
/* We are *done* processing the request,
just queue the response (!) */
- if (UINT_MAX == pc->response_code)
+ if (UINT_MAX == ac->response_code)
{
GNUNET_break (0);
return MHD_NO; /* hard error */
}
res = MHD_queue_response (connection,
- pc->response_code,
- pc->response);
- MHD_destroy_response (pc->response);
- pc->response = NULL;
+ ac->response_code,
+ ac->response);
+ MHD_destroy_response (ac->response);
+ ac->response = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Queueing response (%u) for /pay (%s).\n",
- (unsigned int) pc->response_code,
+ "Queueing response (%u) for /abort (%s).\n",
+ (unsigned int) ac->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_abort (connection,
+ hc,
+ ac);
+ 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;
+
+ /* Abort not finished, suspend while we interact with the exchange */
+ GNUNET_assert (GNUNET_NO == ac->suspended);
+ MHD_suspend_connection (connection);
+ ac->suspended = GNUNET_YES;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Suspending abort handling while working with the exchange\n");
+ ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_TIMEOUT,
+ &handle_abort_timeout,
+ ac);
+ begin_transaction (ac);
+ return MHD_YES;
}
-/* end of taler-merchant-httpd_pay.c */
+/* end of taler-merchant-httpd_post-orders-ID-abort.c */