diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_post-orders-ID-pay.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_post-orders-ID-pay.c | 2438 |
1 files changed, 1174 insertions, 1264 deletions
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 957bfdfa..14edfd55 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2022 Taler Systems SA + (C) 2014-2024 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as @@ -29,7 +29,6 @@ #include <taler/taler_signatures.h> #include <taler/taler_json_lib.h> #include <taler/taler_exchange_service.h> -#include "taler-merchant-httpd_auditors.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_post-orders-ID-pay.h" @@ -60,6 +59,63 @@ struct PayContext; /** + * Different phases of processing the /pay request. + */ +enum PayPhase +{ + /** + * Initial phase where the request is parsed. + */ + PP_INIT = 0, + + /** + * Check database state for the given order. + */ + PP_CHECK_CONTRACT, + + /** + * Contract has been paid. + */ + PP_CONTRACT_PAID, + + /** + * Execute payment transaction. + */ + PP_PAY_TRANSACTION, + + /** + * Notify other processes about successful payment. + */ + PP_PAYMENT_NOTIFICATION, + + /** + * Create final success response. + */ + PP_SUCCESS_RESPONSE, + + /** + * Perform batch deposits with exchange(s). + */ + PP_BATCH_DEPOSITS, + + /** + * Return response in payment context. + */ + PP_RETURN_RESPONSE, + + /** + * Return #MHD_YES to end processing. + */ + PP_END_YES, + + /** + * Return #MHD_NO to end processing. + */ + PP_END_NO +}; + + +/** * Information kept during a pay request for each coin. */ struct DepositConfirmation @@ -71,12 +127,6 @@ struct DepositConfirmation struct PayContext *pc; /** - * Handle to the deposit operation we are performing for - * this coin, NULL after the operation is done. - */ - struct TALER_EXCHANGE_DepositHandle *dh; - - /** * URL of the exchange that issued this coin. */ char *exchange_url; @@ -97,11 +147,6 @@ struct DepositConfirmation struct TALER_Amount refund_fee; /** - * Wire fee charged by the exchange of this coin. - */ - struct TALER_Amount wire_fee; - - /** * If a minimum age was required (i. e. pc->minimum_age is large enough), * this is the signature of the minimum age (as a single uint8_t), using the * private key to the corresponding age group. Might be all zeroes for no @@ -180,7 +225,7 @@ struct ExchangeGroup * the exchange used for this transaction; NULL if no operation is * pending. */ - struct TMH_EXCHANGES_FindOperation *fo; + struct TMH_EXCHANGES_KeysOperation *fo; /** * URL of the exchange that issued this coin. Aliases @@ -189,6 +234,12 @@ struct ExchangeGroup const char *exchange_url; /** + * Wire fee that applies to this exchange for the + * given payment context's wire method. + */ + struct TALER_Amount wire_fee; + + /** * true if we already tried a forced /keys download. */ bool tried_force_keys; @@ -234,7 +285,7 @@ struct PayContext /** * What wire method (of the @e mi) was selected by the wallet? - * Set in #parse_pay(). + * Set in #phase_parse_pay(). */ struct TMH_WireMethod *wm; @@ -297,30 +348,11 @@ struct PayContext * 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). + * (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. */ @@ -368,13 +400,14 @@ struct PayContext struct GNUNET_TIME_Timestamp 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. + * Set to the POS key, if applicable for this order. */ - uint32_t wire_fee_amortization; + char *pos_key; + + /** + * Algorithm chosen for generating the confirmation code. + */ + enum TALER_MerchantConfirmationAlgorithm pos_alg; /** * Minimum age required for this purchase. @@ -385,7 +418,7 @@ struct PayContext * Number of coins this payment is made of. Length * of the @e dc array. */ - unsigned int coins_cnt; + size_t coins_cnt; /** * Number of exchanges involved in the payment. Length @@ -416,6 +449,11 @@ struct PayContext unsigned int response_code; /** + * Payment processing phase we are in. + */ + enum PayPhase phase; + + /** * #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 @@ -423,68 +461,17 @@ struct PayContext */ enum GNUNET_GenericReturnValue suspended; -}; - - -/** - * Active KYC operation with an exchange. - */ -struct KycContext -{ - /** - * Kept in a DLL. - */ - struct KycContext *next; - - /** - * Kept in a DLL. - */ - struct KycContext *prev; - /** - * Looking for the exchange. + * Set to true if the deposit currency of a coin + * does not match the contract currency. */ - struct TMH_EXCHANGES_FindOperation *fo; - - /** - * Exchange this is about. - */ - char *exchange_url; + bool deposit_currency_mismatch; /** - * Merchant instance this is for. + * Set to true if the database contains a (bogus) + * refund for a different currency. */ - struct TMH_MerchantInstance *mi; - - /** - * Wire method we are checking the status of. - */ - struct TMH_WireMethod *wm; - - /** - * Handle for the GET /deposits operation. - */ - struct TALER_EXCHANGE_DepositGetHandle *dg; - - /** - * Contract we are looking up. - */ - struct TALER_PrivateContractHashP h_contract_terms; - - /** - * Coin we are looking up. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Initial DB timestamp. - */ - struct GNUNET_TIME_Timestamp kyc_timestamp; - - /** - * Initial KYC status. - */ - bool kyc_ok; + bool refund_currency_mismatch; }; @@ -499,108 +486,14 @@ static struct PayContext *pc_head; */ static struct PayContext *pc_tail; -/** - * Head of active KYC context DLL. - */ -static struct KycContext *kc_head; - -/** - * Tail of active KYC context DLL. - */ -static struct KycContext *kc_tail; - - -/** - * Free resources used by @a kc. - * - * @param[in] kc object to free - */ -static void -destroy_kc (struct KycContext *kc) -{ - if (NULL != kc->fo) - { - TMH_EXCHANGES_find_exchange_cancel (kc->fo); - kc->fo = NULL; - } - if (NULL != kc->dg) - { - TALER_EXCHANGE_deposits_get_cancel (kc->dg); - kc->dg = NULL; - } - TMH_instance_decref (kc->mi); - kc->mi = NULL; - GNUNET_free (kc->exchange_url); - GNUNET_CONTAINER_DLL_remove (kc_head, - kc_tail, - kc); - GNUNET_free (kc); -} - - -/** - * Compute the timeout for a /pay request based on the number of coins - * involved. - * - * @param num_coins number of coins - * @returns timeout for the /pay request - */ -static struct GNUNET_TIME_Relative -get_pay_timeout (unsigned int num_coins) -{ - struct GNUNET_TIME_Relative t; - - /* FIXME: Do some benchmarking to come up with a better timeout. - * We've increased this value so the wallet integration test passes again - * on my (Florian) machine. - */ - t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, - 15 * (1 + (num_coins / 5))); - - return t; -} - - -/** - * Abort all pending /deposit operations. - * - * @param pc pay context to abort - */ -static void -abort_active_deposits (struct PayContext *pc) -{ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Aborting pending /deposit operations\n"); - for (unsigned int i = 0; i<pc->coins_cnt; i++) - { - struct DepositConfirmation *dci = &pc->dc[i]; - - if (NULL != dci->dh) - { - TALER_EXCHANGE_deposit_cancel (dci->dh); - dci->dh = NULL; - } - } -} - void TMH_force_pc_resume () { - struct KycContext *kc; - - while (NULL != (kc = kc_head)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Aborting KYC check at %s\n", - kc->exchange_url); - destroy_kc (kc); - } for (struct PayContext *pc = pc_head; NULL != pc; pc = pc->next) { - abort_active_deposits (pc); if (NULL != pc->timeout_task) { GNUNET_SCHEDULER_cancel (pc->timeout_task); @@ -616,6 +509,21 @@ TMH_force_pc_resume () /** + * Resume payment processing. + * + * @param[in,out] pc payment process to resume + */ +static void +pay_resume (struct PayContext *pc) +{ + GNUNET_assert (GNUNET_YES == pc->suspended); + pc->suspended = GNUNET_NO; + MHD_resume_connection (pc->connection); + TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ +} + + +/** * Resume the given pay context and send the given response. * Stores the response in the @a pc and signals MHD to resume * the connection. Also ensures MHD runs immediately. @@ -629,21 +537,36 @@ resume_pay_with_response (struct PayContext *pc, unsigned int response_code, struct MHD_Response *response) { - abort_active_deposits (pc); pc->response_code = response_code; pc->response = response; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /pay handling. HTTP status for our reply is %u.\n", response_code); + for (unsigned int i = 0; i<pc->num_exchanges; i++) + { + struct ExchangeGroup *eg = pc->egs[i]; + + if (NULL != eg->fo) + { + TMH_EXCHANGES_keys4exchange_cancel (eg->fo); + eg->fo = NULL; + pc->pending_at_eg--; + } + if (NULL != eg->bdh) + { + TALER_EXCHANGE_batch_deposit_cancel (eg->bdh); + eg->bdh = NULL; + pc->pending_at_eg--; + } + } + GNUNET_assert (0 == pc->pending_at_eg); if (NULL != pc->timeout_task) { GNUNET_SCHEDULER_cancel (pc->timeout_task); pc->timeout_task = NULL; } - GNUNET_assert (GNUNET_YES == pc->suspended); - pc->suspended = GNUNET_NO; - MHD_resume_connection (pc->connection); - TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ + pc->phase = PP_RETURN_RESPONSE; + pay_resume (pc); } @@ -651,292 +574,148 @@ resume_pay_with_response (struct PayContext *pc, * Resume payment processing with an error. * * @param pc 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_pay_with_response (pc, - http_status, - TALER_MHD_make_error (ec, - msg)); -} - - -/** - * Custom cleanup routine for a `struct PayContext`. - * - * @param cls the `struct PayContext` to clean up. - */ -static void -pay_context_cleanup (void *cls) -{ - struct PayContext *pc = cls; - - if (NULL != pc->timeout_task) - { - GNUNET_SCHEDULER_cancel (pc->timeout_task); - pc->timeout_task = NULL; - } - if (NULL != pc->contract_terms) - { - json_decref (pc->contract_terms); - pc->contract_terms = NULL; - } - abort_active_deposits (pc); - for (unsigned int i = 0; i<pc->coins_cnt; i++) - { - struct DepositConfirmation *dc = &pc->dc[i]; - - TALER_denom_sig_free (&dc->cdd.denom_sig); - GNUNET_free (dc->exchange_url); - } - GNUNET_free (pc->dc); - for (unsigned int i = 0; i<pc->num_exchanges; i++) - { - struct ExchangeGroup *eg = pc->egs[i]; - - if (NULL != eg->fo) - TMH_EXCHANGES_find_exchange_cancel (eg->fo); - GNUNET_free (eg); - } - GNUNET_free (pc->egs); - if (NULL != pc->response) - { - MHD_destroy_response (pc->response); - pc->response = NULL; - } - GNUNET_free (pc->fulfillment_url); - GNUNET_free (pc->session_id); - GNUNET_CONTAINER_DLL_remove (pc_head, - pc_tail, - pc); - GNUNET_free (pc); + resume_pay_with_response ( + pc, + TALER_ErrorCode_get_http_status_safe (ec), + TALER_MHD_make_error (ec, + msg)); } /** - * Execute the DB transaction. If required (from - * soft/serialization errors), the transaction can be - * restarted here. - * - * @param pc payment context to transact - */ -static void -execute_pay_transaction (struct PayContext *pc); - - -/** - * Function called with detailed wire transfer data. + * Conclude payment processing for @a pc with the + * given @a res MHD status code. * - * @param cls a `struct KycContext *` - * @param dr HTTP response data + * @param[in,out] pc payment context for final state transition + * @param res MHD return code to end with */ static void -deposit_get_callback ( - void *cls, - const struct TALER_EXCHANGE_GetDepositResponse *dr) +pay_end (struct PayContext *pc, + MHD_RESULT res) { - struct KycContext *kc = cls; - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_TIME_Timestamp now; - - kc->dg = NULL; - now = GNUNET_TIME_timestamp_get (); - switch (dr->hr.http_status) - { - case MHD_HTTP_OK: - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - kc->mi->settings.id, - &kc->wm->h_wire, - kc->exchange_url, - 0LL, - NULL, /* no signature */ - NULL, /* no signature */ - now, - true); - GNUNET_break (qs > 0); - break; - case MHD_HTTP_ACCEPTED: - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - kc->mi->settings.id, - &kc->wm->h_wire, - kc->exchange_url, - dr->details.accepted.requirement_row, - NULL, /* no signature */ - NULL, /* no signature */ - now, - dr->details.accepted.kyc_ok); - GNUNET_break (qs > 0); - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "KYC check failed at %s with unexpected status %u\n", - kc->exchange_url, - dr->hr.http_status); - } - destroy_kc (kc); + pc->phase = (MHD_YES == res) + ? PP_END_YES + : PP_END_NO; } /** - * Function called with the result of our exchange lookup. + * Return response stored in @a pc. * - * @param cls the `struct KycContext` - * @param hr HTTP response details - * @param exchange_handle NULL if exchange was not found to be acceptable - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable fee for dealing with @a exchange_handle, - * NULL if not available - * @param exchange_trusted true if this exchange is - * trusted by config + * @param[in,out] pc payment context we are processing */ static void -process_kyc_with_exchange ( - void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - struct TALER_EXCHANGE_Handle *exchange_handle, - const char *payto_uri, - const struct TALER_Amount *wire_fee, - bool exchange_trusted) +phase_return_response (struct PayContext *pc) { - struct KycContext *kc = cls; - - kc->fo = NULL; - if (NULL == exchange_handle) - { - destroy_kc (kc); - return; - } - kc->dg = TALER_EXCHANGE_deposits_get (exchange_handle, - &kc->mi->merchant_priv, - &kc->wm->h_wire, - &kc->h_contract_terms, - &kc->coin_pub, - &deposit_get_callback, - kc); - if (NULL == kc->dg) + GNUNET_assert (0 != pc->response_code); + /* We are *done* processing the request, just queue the response (!) */ + if (UINT_MAX == pc->response_code) { GNUNET_break (0); - destroy_kc (kc); + pay_end (pc, + MHD_NO); /* hard error */ + return; } + pay_end (pc, + MHD_queue_response (pc->connection, + pc->response_code, + pc->response)); } /** - * Function called from ``account_kyc_get_status`` - * with KYC status information for this merchant. - * - * @param cls a `struct KycContext *` - * @param h_wire hash of the wire account - * @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown - * @param payto_uri payto:// URI of the merchant's bank account - * @param exchange_url base URL of the exchange for which this is a status - * @param last_check when did we last get an update on our KYC status from the exchange - * @param kyc_ok true if we satisfied the KYC requirements - */ -static void -kyc_cb ( - void *cls, - const struct TALER_MerchantWireHashP *h_wire, - uint64_t exchange_kyc_serial, - const char *payto_uri, - const char *exchange_url, - struct GNUNET_TIME_Timestamp last_check, - bool kyc_ok) -{ - struct KycContext *kc = cls; - - kc->kyc_timestamp = last_check; - kc->kyc_ok = kyc_ok; -} - - -/** - * Check for our KYC status at @a exchange_url for the - * payment of @a pc. First checks if we already have a - * positive result from the exchange, and if not checks - * with the exchange. + * Do database transaction for a completed batch deposit. * - * @param pc payment context to use as starting point - * @param eg exchange group of the exchange we are triggering on + * @param eg group that completed + * @param dr response from the server + * @return transaction status */ -static void -check_kyc (struct PayContext *pc, - const struct ExchangeGroup *eg) +static enum GNUNET_DB_QueryStatus +batch_deposit_transaction (const struct ExchangeGroup *eg, + const struct TALER_EXCHANGE_BatchDepositResult *dr) { + const struct PayContext *pc = eg->pc; enum GNUNET_DB_QueryStatus qs; - struct KycContext *kc; + struct TALER_Amount total_without_fees; + uint64_t b_dep_serial; + uint32_t off = 0; - kc = GNUNET_new (struct KycContext); - qs = TMH_db->account_kyc_get_status (TMH_db->cls, - pc->hc->instance->settings.id, - &pc->wm->h_wire, - eg->exchange_url, - &kyc_cb, - kc); - if (qs < 0) - { - GNUNET_break (0); - GNUNET_free (kc); - return; - } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - if (kc->kyc_ok) - { - GNUNET_free (kc); - return; /* we are done */ - } - if (GNUNET_TIME_relative_cmp ( - GNUNET_TIME_absolute_get_duration ( - kc->kyc_timestamp.abs_time), - <, - KYC_RETRY_FREQUENCY)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Not re-checking KYC status at `%s', as we already recently asked\n", - eg->exchange_url); - GNUNET_free (kc); - return; - } - } - kc->mi = pc->hc->instance; - kc->mi->rc++; - kc->wm = pc->wm; - kc->exchange_url = GNUNET_strdup (eg->exchange_url); - kc->h_contract_terms = pc->h_contract_terms; - /* find one of the coins of the batch */ - for (unsigned int i = 0; i<pc->coins_cnt; i++) + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pc->amount.currency, + &total_without_fees)); + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; + struct TALER_Amount amount_without_fees; + /* might want to group deposits by batch more explicitly ... */ if (0 != strcmp (eg->exchange_url, - pc->dc[i].exchange_url)) + dc->exchange_url)) continue; - kc->coin_pub = dc->cdd.coin_pub; - break; - } - GNUNET_CONTAINER_DLL_insert (kc_head, - kc_tail, - kc); - kc->fo = TMH_EXCHANGES_find_exchange (kc->exchange_url, - NULL, - GNUNET_NO, - &process_kyc_with_exchange, - kc); - if (NULL == kc->fo) + if (dc->found_in_db) + continue; + GNUNET_assert (0 <= + TALER_amount_subtract (&amount_without_fees, + &dc->cdd.amount, + &dc->deposit_fee)); + GNUNET_assert (0 <= + TALER_amount_add (&total_without_fees, + &total_without_fees, + &amount_without_fees)); + } + qs = TMH_db->insert_deposit_confirmation ( + TMH_db->cls, + pc->hc->instance->settings.id, + dr->details.ok.deposit_timestamp, + &pc->h_contract_terms, + eg->exchange_url, + pc->wire_transfer_deadline, + &total_without_fees, + &eg->wire_fee, + &pc->wm->h_wire, + dr->details.ok.exchange_sig, + dr->details.ok.exchange_pub, + &b_dep_serial); + if (qs <= 0) + return qs; /* Entire batch already known or failure, we're done */ + + for (size_t i = 0; i<pc->coins_cnt; i++) { - GNUNET_break (0); - destroy_kc (kc); + struct DepositConfirmation *dc = &pc->dc[i]; + + /* might want to group deposits by batch more explicitly ... */ + if (0 != strcmp (eg->exchange_url, + dc->exchange_url)) + continue; + if (dc->found_in_db) + continue; + /* NOTE: We might want to check if the order was fully paid concurrently + by some other wallet here, and if so, issue an auto-refund. Right now, + it is possible to over-pay if two wallets literally make a concurrent + payment, as the earlier check for 'paid' is not in the same transaction + scope as this 'insert' operation. */ + qs = TMH_db->insert_deposit ( + TMH_db->cls, + off++, /* might want to group deposits by batch more explicitly ... */ + b_dep_serial, + &dc->cdd.coin_pub, + &dc->cdd.coin_sig, + &dc->cdd.amount, + &dc->deposit_fee, + &dc->refund_fee); + if (qs < 0) + return qs; + GNUNET_break (qs > 0); } + return qs; } @@ -963,8 +742,6 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, pc->hc->instance->settings.id); for (unsigned int r = 0; r<MAX_RETRIES; r++) { - unsigned int j = 0; - TMH_db->preflight (TMH_db->cls); if (GNUNET_OK != TMH_db->start (TMH_db->cls, @@ -979,51 +756,19 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, TMH_pack_exchange_reply (&dr->hr))); return; } - for (unsigned int i = 0; i<pc->coins_cnt; i++) + qs = batch_deposit_transaction (eg, + dr); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { - struct DepositConfirmation *dc = &pc->dc[i]; - - if (0 != strcmp (eg->exchange_url, - pc->dc[i].exchange_url)) - continue; - if (dc->found_in_db) - continue; - /* NOTE: We might want to check if the order was fully paid concurrently - by some other wallet here, and if so, issue an auto-refund. Right now, - it is possible to over-pay if two wallets literally make a concurrent - payment, as the earlier check for 'paid' is not in the same transaction - scope as this 'insert' operation. */ - GNUNET_assert (j < dr->details.success.num_signatures); - qs = TMH_db->insert_deposit ( - TMH_db->cls, - pc->hc->instance->settings.id, - dr->details.success.deposit_timestamp, - &pc->h_contract_terms, - &dc->cdd.coin_pub, - dc->exchange_url, - &dc->cdd.amount, - &dc->deposit_fee, - &dc->refund_fee, - &dc->wire_fee, - &pc->wm->h_wire, - &dr->details.success.exchange_sigs[j++], - dr->details.success.exchange_pub); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - TMH_db->rollback (TMH_db->cls); - break; - } - if (0 > qs) - { - /* 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_GENERIC_DB_STORE_FAILED, - "insert_deposit"); - return; - } + TMH_db->rollback (TMH_db->cls); + continue; + } + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + resume_pay_with_error (pc, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + "batch_deposit_transaction"); } qs = TMH_db->commit (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -1035,23 +780,21 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, { GNUNET_break (0); resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_COMMIT_FAILED, "insert_deposit"); } break; /* DB transaction succeeded */ - } /* FOR DB retries */ + } if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_SOFT_FAILURE, "insert_deposit"); return; } /* Transaction is done, mark affected coins as complete as well. */ - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1063,8 +806,6 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg, dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */ pc->pending--; } - check_kyc (pc, - eg); } @@ -1083,18 +824,21 @@ batch_deposit_cb ( struct PayContext *pc = eg->pc; eg->bdh = NULL; + pc->pending_at_eg--; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Batch deposit completed with status %u\n", dr->hr.http_status); GNUNET_assert (GNUNET_YES == pc->suspended); - pc->pending_at_eg--; switch (dr->hr.http_status) { case MHD_HTTP_OK: handle_batch_deposit_ok (eg, dr); if (0 == pc->pending_at_eg) - execute_pay_transaction (eg->pc); + { + pc->phase = PP_PAY_TRANSACTION; + pay_resume (pc); + } return; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -1137,8 +881,8 @@ batch_deposit_cb ( TALER_JSON_pack_ec ( TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS), TMH_pack_exchange_reply (&dr->hr), - GNUNET_JSON_pack_data_auto ("exchange_url", - &eg->exchange_url))); + GNUNET_JSON_pack_string ("exchange_url", + eg->exchange_url))); return; } resume_pay_with_response ( @@ -1148,92 +892,102 @@ batch_deposit_cb ( TALER_JSON_pack_ec ( TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS), TMH_pack_exchange_reply (&dr->hr), - GNUNET_JSON_pack_data_auto ("exchange_url", - &eg->exchange_url))); + GNUNET_JSON_pack_string ("exchange_url", + eg->exchange_url))); return; } /* end switch */ } /** - * Function called with the result of our exchange lookup. + * Force re-downloading keys for @a eg. + * + * @param[in,out] eg group to re-download keys for + */ +static void +force_keys (struct ExchangeGroup *eg); + + +/** + * Function called with the result of our exchange keys lookup. * * @param cls the `struct ExchangeGroup` - * @param hr HTTP response details - * @param exchange_handle NULL if exchange was not found to be acceptable - * @param payto_uri payto://-URI of the exchange - * @param wire_fee current applicable fee for dealing with @a exchange_handle, - * NULL if not available - * @param exchange_trusted true if this exchange is - * trusted by config + * @param keys the keys of the exchange + * @param exchange representation of the exchange */ static void -process_pay_with_exchange ( +process_pay_with_keys ( void *cls, - const struct TALER_EXCHANGE_HttpResponse *hr, - struct TALER_EXCHANGE_Handle *exchange_handle, - const char *payto_uri, - const struct TALER_Amount *wire_fee, - bool exchange_trusted) + struct TALER_EXCHANGE_Keys *keys, + struct TMH_Exchange *exchange) { struct ExchangeGroup *eg = cls; struct PayContext *pc = eg->pc; struct TMH_HandlerContext *hc = pc->hc; - const struct TALER_EXCHANGE_Keys *keys; unsigned int group_size; - (void) payto_uri; eg->fo = NULL; + pc->pending_at_eg--; + GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Processing payment with exchange %s\n", - (NULL == exchange_handle) - ? "<null>" - : TALER_EXCHANGE_get_base_url (exchange_handle)); + eg->exchange_url); GNUNET_assert (GNUNET_YES == pc->suspended); - if (NULL == hr) + if (NULL == keys) { - pc->pending_at_eg--; - resume_pay_with_response ( + GNUNET_break_op (0); + resume_pay_with_error ( pc, - MHD_HTTP_GATEWAY_TIMEOUT, - TALER_MHD_MAKE_JSON_PACK ( - TALER_JSON_pack_ec (TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT))); + TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT, + NULL); return; } - if ( (MHD_HTTP_OK != hr->http_status) || - (NULL == exchange_handle) ) + + if (GNUNET_OK != + TMH_exchange_check_debit (exchange, + pc->wm)) { - pc->pending_at_eg--; - resume_pay_with_response ( - pc, - MHD_HTTP_BAD_GATEWAY, - TALER_MHD_MAKE_JSON_PACK ( - TALER_JSON_pack_ec ( - TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE), - TMH_pack_exchange_reply (hr))); + if (eg->tried_force_keys) + { + GNUNET_break_op (0); + resume_pay_with_error ( + pc, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED, + NULL); + return; + } + force_keys (eg); return; } - keys = TALER_EXCHANGE_get_keys (exchange_handle); - if (NULL == keys) + + if (GNUNET_OK != + TMH_EXCHANGES_lookup_wire_fee (exchange, + pc->wm->wire_method, + &eg->wire_fee)) { - pc->pending_at_eg--; - GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK */ - resume_pay_with_error (pc, - MHD_HTTP_BAD_GATEWAY, - TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE, - NULL); + if (eg->tried_force_keys) + { + GNUNET_break_op (0); + resume_pay_with_error ( + pc, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED, + pc->wm->wire_method); + return; + } + force_keys (eg); return; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got wire data for %s\n", + eg->exchange_url); /* Initiate /batch-deposit operation for all coins of the current exchange (!) */ group_size = 0; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; const struct TALER_EXCHANGE_DenomPublicKey *denom_details; - unsigned int http_status; - enum TALER_ErrorCode ec; bool is_age_restricted_denom = false; if (0 != strcmp (eg->exchange_url, @@ -1247,74 +1001,45 @@ process_pay_with_exchange ( &dc->cdd.h_denom_pub); if (NULL == denom_details) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Denomination not found, re-fetching /keys\n"); - if (! eg->tried_force_keys) + if (eg->tried_force_keys) { - /* let's try *forcing* a re-download of /keys from the exchange. - Maybe the wallet has seen /keys that we missed. */ - eg->tried_force_keys = true; - eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, - pc->wm->wire_method, - GNUNET_YES, - &process_pay_with_exchange, - eg); - if (NULL != eg->fo) - return; + GNUNET_break_op (0); + resume_pay_with_response ( + pc, + MHD_HTTP_BAD_REQUEST, + TALER_MHD_MAKE_JSON_PACK ( + TALER_JSON_pack_ec ( + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND), + GNUNET_JSON_pack_data_auto ("h_denom_pub", + &dc->cdd.h_denom_pub), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ( + "exchange_keys", + TALER_EXCHANGE_keys_to_json (keys))))); + return; } - /* Forcing failed or we already did it, give up */ - pc->pending_at_eg--; - resume_pay_with_response ( - pc, - MHD_HTTP_BAD_REQUEST, - TALER_MHD_MAKE_JSON_PACK ( - TALER_JSON_pack_ec ( - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND), - GNUNET_JSON_pack_data_auto ("h_denom_pub", - &dc->cdd.h_denom_pub), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ( - "exchange_keys", - (json_t *) TALER_EXCHANGE_get_keys_raw (exchange_handle))))); + force_keys (eg); return; } dc->deposit_fee = denom_details->fees.deposit; dc->refund_fee = denom_details->fees.refund; - if (GNUNET_OK != - TMH_AUDITORS_check_dk (exchange_handle, - denom_details, - exchange_trusted, - &http_status, - &ec)) + if (GNUNET_TIME_absolute_is_past ( + denom_details->expire_deposit.abs_time)) { - if (! eg->tried_force_keys) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Denomination not audited by trusted auditor, re-fetching /keys\n"); - /* let's try *forcing* a re-download of /keys from the exchange. - Maybe the wallet has seen auditors that we missed. */ - eg->tried_force_keys = true; - eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, - pc->wm->wire_method, - GNUNET_YES, - &process_pay_with_exchange, - eg); - if (NULL != eg->fo) - return; - } - pc->pending_at_eg--; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Denomination key offered by client has expired for deposits\n"); resume_pay_with_response ( pc, - http_status, + MHD_HTTP_GONE, TALER_MHD_MAKE_JSON_PACK ( - TALER_JSON_pack_ec (ec), + TALER_JSON_pack_ec ( + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED), GNUNET_JSON_pack_data_auto ("h_denom_pub", &denom_details->h_key))); return; } - /* Now that we have the details about the denomination, we can verify age * restriction requirements, if applicable. Note that denominations with an * age_mask equal to zero always pass the age verification. */ @@ -1351,7 +1076,7 @@ process_pay_with_exchange ( AGE_FAIL: if (0 < code) { - pc->pending_at_eg--; + GNUNET_break_op (0); GNUNET_free (dc->age_commitment.keys); resume_pay_with_response ( pc, @@ -1369,13 +1094,13 @@ AGE_FAIL: &dc->cdd.h_age_commitment); GNUNET_free (dc->age_commitment.keys); } - else if (is_age_restricted_denom && dc->no_h_age_commitment) + else if (is_age_restricted_denom && + dc->no_h_age_commitment) { /* The contract did not ask for a minimum_age but the client paid * with a coin that has age restriction enabled. We lack the hash * of the age commitment in this case in order to verify the coin * and to deposit it with the exchange. */ - pc->pending_at_eg--; GNUNET_break_op (0); resume_pay_with_response ( pc, @@ -1393,12 +1118,15 @@ AGE_FAIL: if (0 == group_size) { GNUNET_break (0); - pc->pending_at_eg--; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Group size zero, %u batch transactions remain pending\n", pc->pending_at_eg); if (0 == pc->pending_at_eg) - execute_pay_transaction (pc); + { + pc->phase = PP_PAY_TRANSACTION; + pay_resume (pc); + return; + } return; } @@ -1409,14 +1137,14 @@ AGE_FAIL: .merchant_payto_uri = pc->wm->payto_uri, .wire_salt = pc->wm->wire_salt, .h_contract_terms = pc->h_contract_terms, - .policy_details = NULL, /* FIXME-oec #7270 */ - .timestamp = pc->timestamp, + .wallet_timestamp = pc->timestamp, .merchant_pub = hc->instance->merchant_pub, .refund_deadline = pc->refund_deadline }; enum TALER_ErrorCode ec; + size_t off = 0; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1425,25 +1153,27 @@ AGE_FAIL: if (0 != strcmp (dc->exchange_url, eg->exchange_url)) continue; - cdds[i] = dc->cdd; - dc->wire_fee = *wire_fee; + GNUNET_assert (off < group_size); + cdds[off++] = dc->cdd; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Initiating batch deposit with %u coins\n", group_size); - eg->bdh = TALER_EXCHANGE_batch_deposit (exchange_handle, - &dcd, - group_size, - cdds, - &batch_deposit_cb, - eg, - &ec); + eg->bdh = TALER_EXCHANGE_batch_deposit ( + TMH_curl_ctx, + eg->exchange_url, + keys, + &dcd, + group_size, + cdds, + &batch_deposit_cb, + eg, + &ec); if (NULL == eg->bdh) { /* Signature was invalid or some other constraint was not satisfied. If the exchange was unavailable, we'd get that information in the callback. */ - pc->pending_at_eg--; GNUNET_break_op (0); resume_pay_with_response ( pc, @@ -1454,27 +1184,96 @@ AGE_FAIL: eg->exchange_url))); return; } + pc->pending_at_eg++; if (TMH_force_audit) TALER_EXCHANGE_batch_deposit_force_dc (eg->bdh); } } +static void +force_keys (struct ExchangeGroup *eg) +{ + struct PayContext *pc = eg->pc; + + eg->tried_force_keys = true; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Forcing /keys download (once) as wire fees are unknown\n"); + eg->fo = TMH_EXCHANGES_keys4exchange ( + eg->exchange_url, + true, + &process_pay_with_keys, + eg); + if (NULL == eg->fo) + { + GNUNET_break (0); + resume_pay_with_error (pc, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED, + "Failed to lookup exchange by URL"); + return; + } + pc->pending_at_eg++; +} + + +/** + * 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_INFO, + "Resuming pay with error after timeout\n"); + resume_pay_with_error (pc, + TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT, + NULL); +} + + +/** + * Compute the timeout for a /pay request based on the number of coins + * involved. + * + * @param num_coins number of coins + * @returns timeout for the /pay request + */ +static struct GNUNET_TIME_Relative +get_pay_timeout (unsigned int num_coins) +{ + struct GNUNET_TIME_Relative t; + + /* FIXME-Performance-Optimization: Do some benchmarking to come up with a + * better timeout. We've increased this value so the wallet integration + * test passes again on my (Florian) machine. + */ + t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, + 15 * (1 + (num_coins / 5))); + + return t; +} + + /** * Start batch deposits for all exchanges involved * in this payment. * - * @param pc payment context we are processing + * @param[in,out] pc payment context we are processing */ static void -start_batch_deposits (struct PayContext *pc) +phase_batch_deposits (struct PayContext *pc) { for (unsigned int i = 0; i<pc->num_exchanges; i++) { struct ExchangeGroup *eg = pc->egs[i]; bool have_coins = false; - for (unsigned int j = 0; j<pc->coins_cnt; j++) + for (size_t j = 0; j<pc->coins_cnt; j++) { struct DepositConfirmation *dc = &pc->dc[j]; @@ -1488,24 +1287,131 @@ start_batch_deposits (struct PayContext *pc) } if (! have_coins) continue; /* no coins left to deposit at this exchange */ - eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url, - pc->wm->wire_method, - GNUNET_NO, - &process_pay_with_exchange, - eg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Getting /keys for %s\n", + eg->exchange_url); + eg->fo = TMH_EXCHANGES_keys4exchange ( + eg->exchange_url, + false, + &process_pay_with_keys, + eg); if (NULL == eg->fo) { GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED, - "Failed to lookup exchange by URL"); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED, + "Failed to lookup exchange by URL")); return; } pc->pending_at_eg++; } if (0 == pc->pending_at_eg) - execute_pay_transaction (pc); + { + pc->phase = PP_PAY_TRANSACTION; + pay_resume (pc); + return; + } + /* Suspend while we interact with the exchange */ + MHD_suspend_connection (pc->connection); + pc->suspended = GNUNET_YES; + GNUNET_assert (NULL == pc->timeout_task); + pc->timeout_task + = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt), + &handle_pay_timeout, + pc); +} + + +/** + * Generate response (payment successful) + * + * @param[in,out] pc payment context where the payment was successful + */ +static void +phase_success_response (struct PayContext *pc) +{ + struct TALER_MerchantSignatureP sig; + char *pos_confirmation; + + /* Sign on our end (as the payment did go through, even if it may + have been refunded already) */ + TALER_merchant_pay_sign (&pc->h_contract_terms, + &pc->hc->instance->merchant_priv, + &sig); + /* Build the response */ + pos_confirmation = (NULL == pc->pos_key) + ? NULL + : TALER_build_pos_confirmation (pc->pos_key, + pc->pos_alg, + &pc->amount, + pc->timestamp); + pay_end (pc, + TALER_MHD_REPLY_JSON_PACK ( + pc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("pos_confirmation", + pos_confirmation)), + GNUNET_JSON_pack_data_auto ("sig", + &sig))); + GNUNET_free (pos_confirmation); +} + + +/** + * Use database to notify other clients about the + * payment being completed. + * + * @param[in,out] pc context to trigger notification for + */ +static void +phase_payment_notification (struct PayContext *pc) +{ + { + struct TMH_OrderPayEventP pay_eh = { + .header.size = htons (sizeof (pay_eh)), + .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID), + .merchant_pub = pc->hc->instance->merchant_pub + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Notifying clients about payment of order %s\n", + pc->order_id); + GNUNET_CRYPTO_hash (pc->order_id, + strlen (pc->order_id), + &pay_eh.h_order_id); + TMH_db->event_notify (TMH_db->cls, + &pay_eh.header, + NULL, + 0); + } + if ( (NULL != pc->session_id) && + (NULL != pc->fulfillment_url) ) + { + struct TMH_SessionEventP session_eh = { + .header.size = htons (sizeof (session_eh)), + .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), + .merchant_pub = pc->hc->instance->merchant_pub + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Notifying clients about session change to %s for %s\n", + pc->session_id, + pc->fulfillment_url); + GNUNET_CRYPTO_hash (pc->session_id, + strlen (pc->session_id), + &session_eh.h_session_id); + GNUNET_CRYPTO_hash (pc->fulfillment_url, + strlen (pc->fulfillment_url), + &session_eh.h_fulfillment_url); + TMH_db->event_notify (TMH_db->cls, + &session_eh.header, + NULL, + 0); + } + pc->phase = PP_SUCCESS_RESPONSE; } @@ -1518,7 +1424,6 @@ start_batch_deposits (struct PayContext *pc) * @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 of this coin charges */ static void check_coin_paid (void *cls, @@ -1526,12 +1431,11 @@ check_coin_paid (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) + const struct TALER_Amount *refund_fee) { struct PayContext *pc = cls; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1543,13 +1447,26 @@ check_coin_paid (void *cls, (0 != strcmp (exchange_url, dc->exchange_url)) || + (GNUNET_OK != + TALER_amount_cmp_currency (amount_with_fee, + &dc->cdd.amount)) || (0 != TALER_amount_cmp (amount_with_fee, &dc->cdd.amount)) ) continue; /* does not match, skip */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Deposit of coin `%s' already in our DB.\n", TALER_B2S (coin_pub)); - + if ( (GNUNET_OK != + TALER_amount_cmp_currency (&pc->total_paid, + amount_with_fee)) || + (GNUNET_OK != + TALER_amount_cmp_currency (&pc->total_fees_paid, + deposit_fee)) ) + { + GNUNET_break_op (0); + pc->deposit_currency_mismatch = true; + break; + } GNUNET_assert (0 <= TALER_amount_add (&pc->total_paid, &pc->total_paid, @@ -1560,7 +1477,6 @@ check_coin_paid (void *cls, deposit_fee)); dc->deposit_fee = *deposit_fee; dc->refund_fee = *refund_fee; - dc->wire_fee = *wire_fee; dc->cdd.amount = *amount_with_fee; dc->found_in_db = true; pc->pending--; @@ -1593,7 +1509,7 @@ check_coin_refunded (void *cls, an abort-pay refund (an unusual but possible case), we need to make sure that existing refunds are accounted for. */ - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; @@ -1601,6 +1517,14 @@ check_coin_refunded (void *cls, if (0 != GNUNET_memcmp (coin_pub, &dc->cdd.coin_pub)) continue; + if (GNUNET_OK != + TALER_amount_cmp_currency (&pc->total_refunded, + refund_amount)) + { + GNUNET_break (0); + pc->refund_currency_mismatch = true; + break; + } GNUNET_assert (0 <= TALER_amount_add (&pc->total_refunded, &pc->total_refunded, @@ -1623,30 +1547,75 @@ 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) + return TALER_amount_is_zero (&pc->amount); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pc->amount.currency, + &total_wire_fee)); + for (unsigned int i = 0; i < pc->num_exchanges; i++) { - return ((0 == pc->amount.value) && - (0 == pc->amount.fraction)); + if (GNUNET_OK != + TALER_amount_cmp_currency (&total_wire_fee, + &pc->egs[i]->wire_fee)) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + total_wire_fee.currency)); + return false; + } + if (0 > + TALER_amount_add (&total_wire_fee, + &total_wire_fee, + &pc->egs[i]->wire_fee)) + { + GNUNET_break (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED, + "could not add exchange wire fee to total")); + return false; + } } - acc_fee = pc->dc[0].deposit_fee; - total_wire_fee = pc->dc[0].wire_fee; - acc_amount = pc->dc[0].cdd.amount; - /** * 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++) + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pc->amount.currency, + &acc_fee)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pc->amount.currency, + &acc_amount)); + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; GNUNET_assert (dc->found_in_db); + if ( (GNUNET_OK != + TALER_amount_cmp_currency (&acc_fee, + &dc->deposit_fee)) || + (GNUNET_OK != + TALER_amount_cmp_currency (&acc_amount, + &dc->cdd.amount)) ) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + dc->deposit_fee.currency)); + return false; + } if ( (0 > TALER_amount_add (&acc_fee, &dc->deposit_fee, @@ -1658,10 +1627,12 @@ check_payment_sufficient (struct PayContext *pc) { GNUNET_break (0); /* Overflow in these amounts? Very strange. */ - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, - "Overflow adding up amounts"); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, + "Overflow adding up amounts")); return false; } if (1 == @@ -1669,53 +1640,15 @@ check_payment_sufficient (struct PayContext *pc) &dc->cdd.amount)) { GNUNET_break_op (0); - resume_pay_with_error (pc, - MHD_HTTP_BAD_REQUEST, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT, - "Deposit fees exceed coin's contribution"); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT, + "Deposit fees exceed coin's contribution")); return false; } - - /* If exchange differs, add wire fee */ - { - bool new_exchange = true; - - 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) - continue; - - if (GNUNET_OK != - TALER_amount_cmp_currency (&total_wire_fee, - &dc->wire_fee)) - { - GNUNET_break_op (0); - resume_pay_with_error (pc, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - total_wire_fee.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_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED, - "could not add exchange wire fee to total"); - return false; - } - } - } /* deposit loop */ + } /* end deposit loop */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Amount received from wallet: %s\n", @@ -1727,9 +1660,6 @@ check_payment_sufficient (struct PayContext *pc) "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, @@ -1737,53 +1667,34 @@ check_payment_sufficient (struct PayContext *pc) TALER_amount2s (&pc->total_refunded)); /* Now compare exchange wire fee compared to - * what we are willing to pay */ + * what we are willing to pay */ if (GNUNET_YES != TALER_amount_cmp_currency (&total_wire_fee, - &pc->max_wire_fee)) + &acc_fee)) { GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - total_wire_fee.currency); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + total_wire_fee.currency)); return false; } - 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_set_zero (total_wire_fee.currency, - &wire_fee_customer_contribution)); - break; - default: - GNUNET_assert (0); - } - - /* add wire fee contribution to the total fees */ + /* add wire fee to the total fees */ if (0 > TALER_amount_add (&acc_fee, &acc_fee, - &wire_fee_customer_contribution)) + &total_wire_fee)) { GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, - "Overflow adding up amounts"); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, + "Overflow adding up amounts")); return false; } if (-1 == TALER_amount_cmp (&pc->max_fee, @@ -1808,10 +1719,12 @@ check_payment_sufficient (struct PayContext *pc) &pc->amount)) { GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, - "Overflow adding up amounts"); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, + "Overflow adding up amounts")); return false; } } @@ -1832,10 +1745,12 @@ check_payment_sufficient (struct PayContext *pc) &pc->total_refunded)) { GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS, - "refunded amount exceeds total payments"); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS, + "refunded amount exceeds total payments")); return false; } @@ -1847,28 +1762,33 @@ check_payment_sufficient (struct PayContext *pc) &total_needed)) { GNUNET_break_op (0); - resume_pay_with_error (pc, - MHD_HTTP_PAYMENT_REQUIRED, - TALER_EC_MERCHANT_POST_ORDERS_ID_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_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES, - "contract not paid up due to fees (client may have calculated them badly)"); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_PAYMENT_REQUIRED, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED, + "contract not paid up due to refunds")); + return false; } - 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_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT, - "payment insufficient"); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES, + "contract not paid up due to fees (client may have calculated them badly)")); + return false; } + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT, + "payment insufficient")); return false; } return true; @@ -1876,86 +1796,14 @@ check_payment_sufficient (struct PayContext *pc) /** - * Use database to notify other clients about the - * payment being completed. - * - * @param pc context to trigger notification for - */ -static void -trigger_payment_notification (struct PayContext *pc) -{ - { - struct TMH_OrderPayEventP pay_eh = { - .header.size = htons (sizeof (pay_eh)), - .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID), - .merchant_pub = pc->hc->instance->merchant_pub - }; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Notifying clients about payment of order %s\n", - pc->order_id); - GNUNET_CRYPTO_hash (pc->order_id, - strlen (pc->order_id), - &pay_eh.h_order_id); - TMH_db->event_notify (TMH_db->cls, - &pay_eh.header, - NULL, - 0); - } - if ( (NULL != pc->session_id) && - (NULL != pc->fulfillment_url) ) - { - struct TMH_SessionEventP session_eh = { - .header.size = htons (sizeof (session_eh)), - .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), - .merchant_pub = pc->hc->instance->merchant_pub - }; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Notifying clients about session change to %s for %s\n", - pc->session_id, - pc->fulfillment_url); - GNUNET_CRYPTO_hash (pc->session_id, - strlen (pc->session_id), - &session_eh.h_session_id); - GNUNET_CRYPTO_hash (pc->fulfillment_url, - strlen (pc->fulfillment_url), - &session_eh.h_fulfillment_url); - TMH_db->event_notify (TMH_db->cls, - &session_eh.header, - NULL, - 0); - } -} - - -/** - * Generate response (payment successful) + * Execute the DB transaction. If required (from + * soft/serialization errors), the transaction can be + * restarted here. * - * @param[in,out] pc payment context where the payment was successful + * @param[in,out] pc payment context to transact */ static void -generate_success_response (struct PayContext *pc) -{ - struct GNUNET_CRYPTO_EddsaSignature sig; - - /* Sign on our end (as the payment did go through, even if it may - have been refunded already) */ - TALER_merchant_pay_sign (&pc->h_contract_terms, - &pc->hc->instance->merchant_priv, - &sig); - /* Build the response */ - resume_pay_with_response ( - pc, - MHD_HTTP_OK, - TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("sig", - &sig))); -} - - -static void -execute_pay_transaction (struct PayContext *pc) +phase_execute_pay_transaction (struct PayContext *pc) { struct TMH_HandlerContext *hc = pc->hc; const char *instance_id = hc->instance->settings.id; @@ -1964,13 +1812,13 @@ execute_pay_transaction (struct PayContext *pc) if (pc->retry_counter++ > MAX_RETRIES) { GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_SOFT_FAILURE, - NULL); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + NULL)); return; } - GNUNET_assert (GNUNET_YES == pc->suspended); /* Initialize some amount accumulators (used in check_coin_paid(), check_coin_refunded() @@ -1984,7 +1832,7 @@ execute_pay_transaction (struct PayContext *pc) GNUNET_break (GNUNET_OK == TALER_amount_set_zero (pc->amount.currency, &pc->total_refunded)); - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) pc->dc[i].found_in_db = false; pc->pending = pc->coins_cnt; @@ -1995,10 +1843,11 @@ execute_pay_transaction (struct PayContext *pc) "run pay")) { GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_START_FAILED, - NULL); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + NULL)); return; } @@ -2015,21 +1864,28 @@ execute_pay_transaction (struct PayContext *pc) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - execute_pay_transaction (pc); - return; - } + return; /* do it again */ /* 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_GENERIC_DB_FETCH_FAILED, - "lookup deposits"); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup deposits")); + return; + } + if (pc->deposit_currency_mismatch) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, + pc->amount.currency)); return; } } - { enum GNUNET_DB_QueryStatus qs; @@ -2043,16 +1899,24 @@ execute_pay_transaction (struct PayContext *pc) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - execute_pay_transaction (pc); - return; - } + return; /* do it again */ /* 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_GENERIC_DB_FETCH_FAILED, - "lookup refunds"); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup refunds")); + return; + } + if (pc->refund_currency_mismatch) + { + TMH_db->rollback (TMH_db->cls); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "refund currency in database does not match order currency")); return; } } @@ -2062,11 +1926,10 @@ execute_pay_transaction (struct PayContext *pc) { /* we made no DB changes, so we can just rollback */ TMH_db->rollback (TMH_db->cls); - /* Ok, we need to first go to the network to process more coins. We that interaction in *tiny* transactions (hence the rollback above). */ - start_batch_deposits (pc); + pc->phase = PP_BATCH_DEPOSITS; return; } @@ -2094,15 +1957,13 @@ execute_pay_transaction (struct PayContext *pc) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - execute_pay_transaction (pc); - return; - } + return; /* do it again */ GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "mark contract paid"); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "mark contract paid")); return; } } @@ -2113,23 +1974,30 @@ execute_pay_transaction (struct PayContext *pc) pc->order_serial); { enum GNUNET_DB_QueryStatus qs; - + json_t *jhook; + + jhook = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_object_incref ("contract_terms", + pc->contract_terms), + GNUNET_JSON_pack_string ("order_id", + pc->order_id) + ); + GNUNET_assert (NULL != jhook); qs = TMH_trigger_webhook (pc->hc->instance->settings.id, "pay", - pc->contract_terms); + jhook); + json_decref (jhook); if (qs < 0) { TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - execute_pay_transaction (pc); - return; - } + return; /* do it again */ GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "failed to trigger webhooks"); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "failed to trigger webhooks")); return; } } @@ -2143,237 +2011,17 @@ execute_pay_transaction (struct PayContext *pc) /* commit failed */ TMH_db->rollback (TMH_db->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - { - execute_pay_transaction (pc); - return; - } + return; /* do it again */ GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - NULL); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL)); return; } - trigger_payment_notification (pc); } - generate_success_response (pc); -} - - -/** - * Try to parse the pay request into the given pay context. - * Schedules an error response in the connection on failure. - * - * @param[in,out] 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 PayContext *pc) -{ - const char *session_id = NULL; - json_t *coins; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("coins", - &coins), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("session_id", - &session_id), - NULL), - GNUNET_JSON_spec_end () - }; - - { - enum GNUNET_GenericReturnValue res; - - res = TALER_MHD_parse_json_data (pc->connection, - pc->hc->request_body, - spec); - if (GNUNET_YES != res) - { - GNUNET_break_op (0); - return res; - } - } - - /* copy session ID (if set) */ - if (NULL != session_id) - { - pc->session_id = GNUNET_strdup (session_id); - } - else - { - /* use empty string as default if client didn't specify it */ - pc->session_id = GNUNET_strdup (""); - } - - if (! json_is_array (coins)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - "'coins' must be an array")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - - pc->coins_cnt = json_array_size (coins); - if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error ( - pc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "'coins' array too long")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - - /* 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 DepositConfirmation *dc = &pc->dc[coins_index]; - const char *exchange_url; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &dc->cdd.coin_sig), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &dc->cdd.coin_pub), - TALER_JSON_spec_denom_sig ("ub_sig", - &dc->cdd.denom_sig), - GNUNET_JSON_spec_fixed_auto ("h_denom", - &dc->cdd.h_denom_pub), - TALER_JSON_spec_amount ("contribution", - TMH_currency, - &dc->cdd.amount), - GNUNET_JSON_spec_string ("exchange_url", - &exchange_url), - /* if a minimum age was required, the minimum_age_sig and - * age_commitment must be provided */ - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("minimum_age_sig", - &dc->minimum_age_sig), - &dc->no_minimum_age_sig), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_age_commitment ("age_commitment", - &dc->age_commitment), - &dc->no_age_commitment), - /* if minimum age was not required, but coin with age restriction set - * was used, h_age_commitment must be provided. */ - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &dc->cdd.h_age_commitment), - &dc->no_h_age_commitment), - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue res; - bool have_eg = false; - - res = TALER_MHD_parse_json_data (pc->connection, - coin, - ispec); - if (GNUNET_YES != res) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return res; - } - - for (unsigned int j = 0; j<coins_index; j++) - { - if (0 == - GNUNET_memcmp (&dc->cdd.coin_pub, - &pc->dc[j].cdd.coin_pub)) - { - GNUNET_break_op (0); - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "duplicate coin in list")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - } - - dc->exchange_url = GNUNET_strdup (exchange_url); - dc->index = coins_index; - dc->pc = pc; - - if (0 != - strcasecmp (dc->cdd.amount.currency, - TMH_currency)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - TMH_currency)) - ? GNUNET_NO - : GNUNET_SYSERR; - } - - /* Check the consistency of the (potential) age restriction - * information. */ - if (dc->no_age_commitment != dc->no_minimum_age_sig) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return (MHD_YES == - TALER_MHD_reply_with_error ( - pc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "inconsistent: 'age_commitment' vs. 'minimum_age_sig'" - ) - ) - ? GNUNET_NO - : GNUNET_SYSERR; - } - - /* Setup exchange group */ - for (unsigned int i = 0; i<pc->num_exchanges; i++) - { - if (0 == - strcmp (pc->egs[i]->exchange_url, - GNUNET_strdup (exchange_url))) - { - have_eg = true; - break; - } - } - if (! have_eg) - { - struct ExchangeGroup *eg; - - eg = GNUNET_new (struct ExchangeGroup); - eg->pc = pc; - eg->exchange_url = dc->exchange_url; - GNUNET_array_append (pc->egs, - pc->num_exchanges, - eg); - } - } - } - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; + pc->phase = PP_PAYMENT_NOTIFICATION; } @@ -2384,9 +2032,10 @@ parse_pay (struct PayContext *pc) * @param cls closure with our `struct PayContext *` * @param deposit_serial which deposit operation is this about * @param exchange_url URL of the exchange that issued the coin + * @param h_wire hash of merchant's wire details + * @param deposit_timestamp when was the deposit made * @param amount_with_fee amount the exchange will deposit for this coin * @param deposit_fee fee the exchange will charge for this coin - * @param h_wire hash of merchant's wire details * @param coin_pub public key of the coin */ static void @@ -2395,13 +2044,14 @@ deposit_paid_check ( uint64_t deposit_serial, const char *exchange_url, const struct TALER_MerchantWireHashP *h_wire, + struct GNUNET_TIME_Timestamp deposit_timestamp, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_CoinSpendPublicKeyP *coin_pub) { struct PayContext *pc = cls; - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dci = &pc->dc[i]; @@ -2411,6 +2061,9 @@ deposit_paid_check ( (0 == strcmp (dci->exchange_url, exchange_url)) && + (GNUNET_YES == + TALER_amount_cmp_currency (&dci->cdd.amount, + amount_with_fee)) && (0 == TALER_amount_cmp (&dci->cdd.amount, amount_with_fee)) ) @@ -2427,11 +2080,9 @@ deposit_paid_check ( * the payment is idempotent, or refunds the excess payment. * * @param[in,out] pc context we use to handle the payment - * @return #GNUNET_NO if response was queued with MHD - * #GNUNET_SYSERR on hard error (MHD connection must be dropped) */ -static enum GNUNET_GenericReturnValue -handle_contract_paid (struct PayContext *pc) +static void +phase_contract_paid (struct PayContext *pc) { enum GNUNET_DB_QueryStatus qs; bool unmatched = false; @@ -2444,15 +2095,14 @@ handle_contract_paid (struct PayContext *pc) if (qs <= 0) { GNUNET_break (0); - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup_deposits_by_order")) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_deposits_by_order")); + return; } - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dci = &pc->dc[i]; @@ -2462,7 +2112,7 @@ handle_contract_paid (struct PayContext *pc) if (! unmatched) { /* Everything fine, idempotent request */ - struct GNUNET_CRYPTO_EddsaSignature sig; + struct TALER_MerchantSignatureP sig; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Idempotent pay request for order `%s', signing again\n", @@ -2470,14 +2120,13 @@ handle_contract_paid (struct PayContext *pc) TALER_merchant_pay_sign (&pc->h_contract_terms, &pc->hc->instance->merchant_priv, &sig); - return (MHD_YES == - TALER_MHD_REPLY_JSON_PACK ( - pc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_data_auto ("sig", - &sig))) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_REPLY_JSON_PACK ( + pc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_data_auto ("sig", + &sig))); + return; } /* Conflict, double-payment detected! */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -2485,7 +2134,7 @@ handle_contract_paid (struct PayContext *pc) pc->order_id); refunds = json_array (); GNUNET_assert (NULL != refunds); - for (unsigned int i = 0; i<pc->coins_cnt; i++) + for (size_t i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dci = &pc->dc[i]; struct TALER_MerchantSignatureP merchant_sig; @@ -2514,29 +2163,25 @@ handle_contract_paid (struct PayContext *pc) GNUNET_JSON_pack_uint64 ("rtransaction_id", 0)))); } - return (MHD_YES == - TALER_MHD_REPLY_JSON_PACK ( - pc->connection, - MHD_HTTP_CONFLICT, - TALER_MHD_PACK_EC ( - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID), - GNUNET_JSON_pack_array_steal ("refunds", - refunds))) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_REPLY_JSON_PACK ( + pc->connection, + MHD_HTTP_CONFLICT, + TALER_MHD_PACK_EC ( + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID), + GNUNET_JSON_pack_array_steal ("refunds", + refunds))); } /** - * Check the database state for the given order. * Schedules an error response in the connection on failure. + * Check the database state for the given order. + * Schedules an error response in the connection on failure. * - * @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[in,out] pc context we use to handle the payment */ -static enum GNUNET_GenericReturnValue -check_contract (struct PayContext *pc) +static void +phase_check_contract (struct PayContext *pc) { /* obtain contract terms */ enum GNUNET_DB_QueryStatus qs; @@ -2547,13 +2192,15 @@ check_contract (struct PayContext *pc) json_decref (pc->contract_terms); pc->contract_terms = NULL; } - qs = TMH_db->lookup_contract_terms (TMH_db->cls, - pc->hc->instance->settings.id, - pc->order_id, - &pc->contract_terms, - &pc->order_serial, - &paid, - NULL); + qs = TMH_db->lookup_contract_terms2 (TMH_db->cls, + pc->hc->instance->settings.id, + pc->order_id, + &pc->contract_terms, + &pc->order_serial, + &paid, + NULL, + &pc->pos_key, + &pc->pos_alg); if (0 > qs) { /* single, read-only SQL statements should never cause @@ -2561,44 +2208,45 @@ check_contract (struct PayContext *pc) 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 (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "contract terms")) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "contract terms")); + return; } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN, - pc->order_id)) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN, + pc->order_id)); + return; } /* hash contract (needed later) */ + json_dumpf (pc->contract_terms, + stderr, + JSON_INDENT (2)); if (GNUNET_OK != TALER_JSON_contract_hash (pc->contract_terms, &pc->h_contract_terms)) { GNUNET_break (0); - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, - NULL)) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, + NULL)); + return; } if (paid) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order `%s' paid, checking for double-payment\n", pc->order_id); - return handle_contract_paid (pc); + pc->phase = PP_CONTRACT_PAID; + return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling payment for order `%s' with contract hash `%s'\n", @@ -2611,34 +2259,27 @@ check_contract (struct PayContext *pc) { /* invalid contract */ GNUNET_break (0); - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING, - NULL)) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING, + NULL)); + return; } /* Get details from contract and check fundamentals */ { const char *fulfillment_url = NULL; struct GNUNET_JSON_Specification espec[] = { - TALER_JSON_spec_amount ("amount", - TMH_currency, - &pc->amount), + TALER_JSON_spec_amount_any ("amount", + &pc->amount), GNUNET_JSON_spec_mark_optional ( + /* This one does not have to be a Web URL */ GNUNET_JSON_spec_string ("fulfillment_url", &fulfillment_url), NULL), - TALER_JSON_spec_amount ("max_fee", - TMH_currency, - &pc->max_fee), - TALER_JSON_spec_amount ("max_wire_fee", - TMH_currency, - &pc->max_wire_fee), - GNUNET_JSON_spec_uint32 ("wire_fee_amortization", - &pc->wire_fee_amortization), + TALER_JSON_spec_amount_any ("max_fee", + &pc->max_fee), GNUNET_JSON_spec_timestamp ("timestamp", &pc->timestamp), GNUNET_JSON_spec_timestamp ("refund_deadline", @@ -2666,7 +2307,42 @@ check_contract (struct PayContext *pc) if (GNUNET_YES != res) { GNUNET_break (0); - return res; + pay_end (pc, + (GNUNET_NO == res) + ? MHD_YES + : MHD_NO); + return; + } + } + + if (GNUNET_OK != + TALER_amount_cmp_currency (&pc->max_fee, + &pc->amount)) + { + GNUNET_break (0); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "'max_fee' in database does not match currency of contract price")); + return; + } + + for (size_t i = 0; i<pc->coins_cnt; i++) + { + struct DepositConfirmation *dc = &pc->dc[i]; + + if (GNUNET_OK != + TALER_amount_cmp_currency (&dc->cdd.amount, + &pc->amount)) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, + pc->amount.currency)); + return; } } @@ -2676,21 +2352,22 @@ check_contract (struct PayContext *pc) { /* This should already have been checked when creating the order! */ GNUNET_break (0); - return TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE, - NULL); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE, + NULL)); + return; } if (GNUNET_TIME_absolute_is_past (pc->pay_deadline.abs_time)) { /* too late */ - return (MHD_YES == - TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_GONE, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED, - NULL)) - ? GNUNET_NO - : GNUNET_SYSERR; + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_GONE, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED, + NULL)); + return; } /* Make sure wire method (still) exists for this instance */ @@ -2704,36 +2381,252 @@ check_contract (struct PayContext *pc) if (NULL == wm) { GNUNET_break (0); - return TALER_MHD_reply_with_error (pc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN, - NULL); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN, + NULL)); + return; } pc->wm = wm; } + pc->phase = PP_PAY_TRANSACTION; +} + + +/** + * Try to parse the pay request into the given pay context. + * Schedules an error response in the connection on failure. + * + * @param[in,out] pc context we use to handle the payment + */ +static void +phase_parse_pay (struct PayContext *pc) +{ + const char *session_id = NULL; + const json_t *coins; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("coins", + &coins), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("session_id", + &session_id), + NULL), + GNUNET_JSON_spec_end () + }; + + GNUNET_assert (PP_INIT == pc->phase); + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (pc->connection, + pc->hc->request_body, + spec); + if (GNUNET_YES != res) + { + GNUNET_break_op (0); + pay_end (pc, + (GNUNET_NO == res) + ? MHD_YES + : MHD_NO); + return; + } + } + + /* copy session ID (if set) */ + if (NULL != session_id) + { + pc->session_id = GNUNET_strdup (session_id); + } + else + { + /* use empty string as default if client didn't specify it */ + pc->session_id = GNUNET_strdup (""); + } + pc->coins_cnt = json_array_size (coins); + if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "'coins' array too long")); + return; + } + + /* 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 DepositConfirmation *dc = &pc->dc[coins_index]; + const char *exchange_url; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &dc->cdd.coin_sig), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &dc->cdd.coin_pub), + TALER_JSON_spec_denom_sig ("ub_sig", + &dc->cdd.denom_sig), + GNUNET_JSON_spec_fixed_auto ("h_denom", + &dc->cdd.h_denom_pub), + TALER_JSON_spec_amount_any ("contribution", + &dc->cdd.amount), + TALER_JSON_spec_web_url ("exchange_url", + &exchange_url), + /* if a minimum age was required, the minimum_age_sig and + * age_commitment must be provided */ + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("minimum_age_sig", + &dc->minimum_age_sig), + &dc->no_minimum_age_sig), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_age_commitment ("age_commitment", + &dc->age_commitment), + &dc->no_age_commitment), + /* if minimum age was not required, but coin with age restriction set + * was used, h_age_commitment must be provided. */ + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &dc->cdd.h_age_commitment), + &dc->no_h_age_commitment), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + bool have_eg = false; + + res = TALER_MHD_parse_json_data (pc->connection, + coin, + ispec); + if (GNUNET_YES != res) + { + GNUNET_break_op (0); + pay_end (pc, + (GNUNET_NO == res) + ? MHD_YES + : MHD_NO); + return; + } + for (unsigned int j = 0; j<coins_index; j++) + { + if (0 == + GNUNET_memcmp (&dc->cdd.coin_pub, + &pc->dc[j].cdd.coin_pub)) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error (pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "duplicate coin in list")); + return; + } + } + + dc->exchange_url = GNUNET_strdup (exchange_url); + dc->index = coins_index; + dc->pc = pc; - return GNUNET_OK; + /* Check the consistency of the (potential) age restriction + * information. */ + if (dc->no_age_commitment != dc->no_minimum_age_sig) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "inconsistent: 'age_commitment' vs. 'minimum_age_sig'" + )); + return; + } + + /* Setup exchange group */ + for (unsigned int i = 0; i<pc->num_exchanges; i++) + { + if (0 == + strcmp (pc->egs[i]->exchange_url, + exchange_url)) + { + have_eg = true; + break; + } + } + if (! have_eg) + { + struct ExchangeGroup *eg; + + eg = GNUNET_new (struct ExchangeGroup); + eg->pc = pc; + eg->exchange_url = dc->exchange_url; + GNUNET_array_append (pc->egs, + pc->num_exchanges, + eg); + } + } + } + pc->phase = PP_CHECK_CONTRACT; } /** - * Handle a timeout for the processing of the pay request. + * Custom cleanup routine for a `struct PayContext`. * - * @param cls our `struct PayContext` + * @param cls the `struct PayContext` to clean up. */ static void -handle_pay_timeout (void *cls) +pay_context_cleanup (void *cls) { struct PayContext *pc = cls; - pc->timeout_task = NULL; - GNUNET_assert (GNUNET_YES == pc->suspended); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming pay with error after timeout\n"); - resume_pay_with_error (pc, - MHD_HTTP_GATEWAY_TIMEOUT, - TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT, - NULL); + if (NULL != pc->timeout_task) + { + GNUNET_SCHEDULER_cancel (pc->timeout_task); + pc->timeout_task = NULL; + } + if (NULL != pc->contract_terms) + { + json_decref (pc->contract_terms); + pc->contract_terms = NULL; + } + for (unsigned int i = 0; i<pc->coins_cnt; i++) + { + struct DepositConfirmation *dc = &pc->dc[i]; + + TALER_denom_sig_free (&dc->cdd.denom_sig); + GNUNET_free (dc->exchange_url); + } + GNUNET_free (pc->dc); + for (unsigned int i = 0; i<pc->num_exchanges; i++) + { + struct ExchangeGroup *eg = pc->egs[i]; + + if (NULL != eg->fo) + TMH_EXCHANGES_keys4exchange_cancel (eg->fo); + GNUNET_free (eg); + } + GNUNET_free (pc->egs); + if (NULL != pc->response) + { + MHD_destroy_response (pc->response); + pc->response = NULL; + } + GNUNET_free (pc->fulfillment_url); + GNUNET_free (pc->session_id); + GNUNET_CONTAINER_DLL_remove (pc_head, + pc_tail, + pc); + GNUNET_free (pc->pos_key); + GNUNET_free (pc); } @@ -2743,59 +2636,76 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, struct TMH_HandlerContext *hc) { struct PayContext *pc = hc->ctx; - enum GNUNET_GenericReturnValue ret; GNUNET_assert (NULL != hc->infix); if (NULL == pc) { pc = GNUNET_new (struct PayContext); - GNUNET_CONTAINER_DLL_insert (pc_head, - pc_tail, - pc); pc->connection = connection; pc->hc = hc; pc->order_id = hc->infix; hc->ctx = pc; hc->cc = &pay_context_cleanup; - ret = parse_pay (pc); - if (GNUNET_OK != ret) - return (GNUNET_NO == ret) - ? MHD_YES - : MHD_NO; - } - if (GNUNET_SYSERR == pc->suspended) - return MHD_NO; /* during shutdown, we don't generate any more replies */ - GNUNET_assert (GNUNET_NO == pc->suspended); - if (0 != pc->response_code) - { - /* We are *done* processing the request, just queue the response (!) */ - if (UINT_MAX == pc->response_code) + GNUNET_CONTAINER_DLL_insert (pc_head, + pc_tail, + pc); + } + while (1) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing /pay in phase %d\n", + (int) pc->phase); + switch (pc->phase) { - GNUNET_break (0); - return MHD_NO; /* hard error */ + case PP_INIT: + phase_parse_pay (pc); + break; + case PP_CHECK_CONTRACT: + phase_check_contract (pc); + break; + case PP_CONTRACT_PAID: + phase_contract_paid (pc); + break; + case PP_PAY_TRANSACTION: + phase_execute_pay_transaction (pc); + break; + case PP_BATCH_DEPOSITS: + phase_batch_deposits (pc); + break; + case PP_PAYMENT_NOTIFICATION: + phase_payment_notification (pc); + break; + case PP_SUCCESS_RESPONSE: + phase_success_response (pc); + break; + case PP_RETURN_RESPONSE: + phase_return_response (pc); + break; + case PP_END_YES: + return MHD_YES; + case PP_END_NO: + return MHD_NO; + } + switch (pc->suspended) + { + case GNUNET_SYSERR: + /* during shutdown, we don't generate any more replies */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing /pay ends due to shutdown in phase %d\n", + (int) pc->phase); + return MHD_NO; + case GNUNET_NO: + /* continue to next phase */ + break; + case GNUNET_YES: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing /pay suspended in phase %d\n", + (int) pc->phase); + return MHD_YES; } - return MHD_queue_response (connection, - pc->response_code, - pc->response); } - ret = check_contract (pc); - if (GNUNET_OK != ret) - return (GNUNET_NO == ret) - ? MHD_YES - : MHD_NO; - - /* Payment not finished, suspend while we interact with the exchange */ - 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 (get_pay_timeout (pc->coins_cnt), - &handle_pay_timeout, - pc); - GNUNET_assert (NULL != pc->wm); - execute_pay_transaction (pc); + /* impossible to get here */ + GNUNET_assert (0); return MHD_YES; } |