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