diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/taler-merchant-httpd_pay.c | 1251 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_responses.c | 33 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_responses.h | 34 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_track-transaction.c | 6 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_track-transfer.c | 10 | ||||
-rw-r--r-- | src/include/taler_merchant_service.h | 68 | ||||
-rw-r--r-- | src/lib/merchant_api_pay.c | 20 | ||||
-rw-r--r-- | src/lib/test_merchant_api.c | 3 | ||||
-rw-r--r-- | src/merchant-tools/taler-merchant-generate-payments.c | 2 |
9 files changed, 859 insertions, 568 deletions
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c index 1315b9c5..8ae6f7d5 100644 --- a/src/backend/taler-merchant-httpd_pay.c +++ b/src/backend/taler-merchant-httpd_pay.c @@ -40,9 +40,9 @@ #define PAY_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)) /** - * How often do we retry the simple INSERT database transaction? + * How often do we retry the (complex!) database transaction? */ -#define MAX_RETRIES 3 +#define MAX_RETRIES 5 /** * Information we keep for an individual call to the /pay handler. @@ -67,6 +67,11 @@ struct DepositConfirmation struct TALER_EXCHANGE_DepositHandle *dh; /** + * URL of the exchange that issued this coin. + */ + char *exchange_url; + + /** * Denomination of this coin. */ struct TALER_DenominationPublicKey denom; @@ -88,6 +93,11 @@ struct DepositConfirmation struct TALER_Amount refund_fee; /** + * Wire fee charged by the exchange of this coin. + */ + struct TALER_Amount wire_fee; + + /** * Public key of the coin. */ struct TALER_CoinSpendPublicKeyP coin_pub; @@ -185,14 +195,14 @@ struct PayContext struct TMH_EXCHANGES_FindOperation *fo; /** - * Placeholder for #TMH_PARSE_post_json() to keep its internal state. + * URL of the exchange used for the last @e fo. */ - void *json_parse_context; - + const char *current_exchange; + /** - * Exchange URI given in @e root. + * Placeholder for #TMH_PARSE_post_json() to keep its internal state. */ - char *chosen_exchange; + void *json_parse_context; /** * Transaction ID given in @e root. @@ -211,6 +221,16 @@ struct PayContext struct GNUNET_HashCode h_wire; /** + * Total wire fees charged by all exchanges involved. Note: there + * is a sublte issue with this value not being correctly calculated + * if /pay is called a second time, as then some deposits that are + * already in the DB are no longer mapped to an exchange (and thus + * no fee is looked up). Fixing this would be rather complicated, + * and is likely simply no worth it. + */ + struct TALER_Amount total_wire_fee; + + /** * Maximum fee the merchant is willing to pay, from @e root. * Note that IF the total fee of the exchange is higher, that is * acceptable to the merchant if the customer is willing to @@ -244,6 +264,26 @@ struct PayContext struct TALER_Amount amount; /** + * Considering all the coins with the "found_in_db" flag + * set, what is the total amount we were so far paid on + * this contract? + */ + struct TALER_Amount total_paid; + + /** + * Considering all the coins with the "found_in_db" flag + * set, what is the total amount we had to pay in deposit + * fees so far on this contract? + */ + struct TALER_Amount total_fees_paid; + + /** + * Considering all the coins with the "found_in_db" flag + * set, what is the total amount we already refunded? + */ + struct TALER_Amount total_refunded; + + /** * Wire transfer deadline. How soon would the merchant like the * wire transfer to be executed? (Can be given by the frontend * or be determined by our configuration via #wire_transfer_delay.) @@ -281,6 +321,11 @@ struct PayContext unsigned int coins_cnt; /** + * How often have we retried the 'main' transaction? + */ + unsigned int retry_counter; + + /** * Number of transactions still pending. Initially set to * @e coins_cnt, decremented on each transaction that * successfully finished. @@ -288,6 +333,14 @@ struct PayContext unsigned int pending; /** + * Number of transactions still pending for the currently selected + * exchange. Initially set to the number of coins started at the + * exchange, decremented on each transaction that successfully + * finished. Once it hits zero, we pick the next exchange. + */ + unsigned int pending_at_ce; + + /** * HTTP status code to use for the reply, i.e 200 for "OK". * Special value UINT_MAX is used to indicate hard errors * (no reply, return #MHD_NO). @@ -295,14 +348,6 @@ struct PayContext unsigned int response_code; /** - * #GNUNET_NO if the transaction is not in our database, - * #GNUNET_YES if the transaction is known to our database, - * #GNUNET_SYSERR if the transaction ID is used for a different - * transaction in our database. - */ - int transaction_exists; - - /** * #GNUNET_NO if the @e connection was not suspended, * #GNUNET_YES if the @e connection was suspended, * #GNUNET_SYSERR if @e connection was resumed to as @@ -409,16 +454,18 @@ sign_success_response (struct PayContext *pc) json_t *refunds; enum TALER_ErrorCode ec; const char *errmsg; - - refunds = TM_get_refund_json (pc->mi, &pc->h_contract_terms, &ec, &errmsg); - - if (NULL == refunds) { - return TMH_RESPONSE_make_internal_error (ec, errmsg); - } - struct GNUNET_CRYPTO_EddsaSignature sig; struct PaymentResponsePS mr; + refunds = TM_get_refund_json (pc->mi, + &pc->h_contract_terms, + &ec, + &errmsg); + + if (NULL == refunds) + return TMH_RESPONSE_make_error (ec, + errmsg); + mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK); mr.purpose.size = htonl (sizeof (mr)); mr.h_contract_terms = pc->h_contract_terms; @@ -441,140 +488,23 @@ sign_success_response (struct PayContext *pc) /** - * Callback to handle a deposit permission's response. + * Resume payment processing with an error. * - * @param cls a `struct DepositConfirmation` (i.e. a pointer - * into the global array of confirmations and an index for this call - * in that array). That way, the last executed callback can detect - * that no other confirmations are on the way, and can pack a response - * for the wallet - * @param http_status HTTP response code, #MHD_HTTP_OK - * (200) for successful deposit; 0 if the exchange's reply is bogus (fails - * to follow the protocol) - * @param ec taler-specific error code, #TALER_EC_NONE on success - * @param sign_key which key did the exchange use to sign the @a proof - * @param proof the received JSON reply, - * should be kept as proof (and, in case of errors, be forwarded to - * the customer) - */ + * @param pc operation to resume + * @param http_status http status code to return + * @param ec taler error code to return + * @param msg human readable error message + */ static void -deposit_cb (void *cls, - unsigned int http_status, - enum TALER_ErrorCode ec, - const struct TALER_ExchangePublicKeyP *sign_key, - const json_t *proof) +resume_pay_with_error (struct PayContext *pc, + unsigned int http_status, + enum TALER_ErrorCode ec, + const char *msg) { - struct DepositConfirmation *dc = cls; - struct PayContext *pc = dc->pc; - enum GNUNET_DB_QueryStatus qs; - - dc->dh = NULL; - GNUNET_assert (GNUNET_YES == pc->suspended); - pc->pending--; - if (MHD_HTTP_OK != http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Deposit operation failed with HTTP code %u\n", - http_status); - /* Transaction failed; stop all other ongoing deposits */ - abort_deposit (pc); - db->rollback (db->cls); - - if (NULL == proof) - { - /* We can't do anything meaningful here, the exchange did something wrong */ - resume_pay_with_response (pc, - MHD_HTTP_SERVICE_UNAVAILABLE, - TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:I, s:s}", - "error", "exchange failed", - "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED, - "exchange-code", (json_int_t) ec, - "exchange-http-status", (json_int_t) http_status, - "hint", "The exchange provided an unexpected response")); - } - else - { - /* Forward error, adding the "coin_pub" for which the - error was being generated */ - json_t *eproof; - - eproof = json_copy ((json_t *) proof); - json_object_set_new (eproof, - "coin_pub", - GNUNET_JSON_from_data_auto (&dc->coin_pub)); - resume_pay_with_response (pc, - http_status, - TMH_RESPONSE_make_json (eproof)); - json_decref (eproof); - } - return; - } - /* store result to DB */ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Storing successful payment for h_contract_terms `%s' and merchant `%s'\n", - GNUNET_h2s (&pc->h_contract_terms), - TALER_B2S (&pc->mi->pubkey)); - for (unsigned int i=0;i<MAX_RETRIES;i++) - { - qs = db->store_deposit (db->cls, - &pc->h_contract_terms, - &pc->mi->pubkey, - &dc->coin_pub, - pc->chosen_exchange, - &dc->amount_with_fee, - &dc->deposit_fee, - &dc->refund_fee, - sign_key, - proof); - if (GNUNET_DB_STATUS_SOFT_ERROR != qs) - break; - } - if (0 > qs) - { - /* Special report if retries insufficient */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); - /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - /* internal error */ - abort_deposit (pc); - db->rollback (db->cls); - /* Forward error including 'proof' for the body */ - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_DB_STORE_PAY_ERROR, - "Merchant database error")); - return; - } - - if (0 != pc->pending) - return; /* still more to do */ - - qs = db->mark_proposal_paid (db->cls, - &pc->h_contract_terms, - &pc->mi->pubkey); - if (0 > qs) - { - abort_deposit (pc); - db->rollback (db->cls); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR, - "Merchant database error: could not mark proposal as 'paid'")); - return; - } - qs = db->commit (db->cls); - if (0 > qs) - { - abort_deposit (pc); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR, - "Merchant database error: could not commit")); - return; - } resume_pay_with_response (pc, - MHD_HTTP_OK, - sign_success_response (pc)); + http_status, + TMH_RESPONSE_make_error (ec, + msg)); } @@ -613,6 +543,7 @@ pay_context_cleanup (struct TM_HandlerContext *hc) GNUNET_CRYPTO_rsa_signature_free (dc->ub_sig.rsa_signature); dc->ub_sig.rsa_signature = NULL; } + GNUNET_free_non_null (dc->exchange_url); } GNUNET_free_non_null (pc->dc); if (NULL != pc->fo) @@ -625,11 +556,6 @@ pay_context_cleanup (struct TM_HandlerContext *hc) MHD_destroy_response (pc->response); pc->response = NULL; } - if (NULL != pc->chosen_exchange) - { - GNUNET_free (pc->chosen_exchange); - pc->chosen_exchange = NULL; - } if (NULL != pc->contract_terms) { json_decref (pc->contract_terms); @@ -643,99 +569,36 @@ pay_context_cleanup (struct TM_HandlerContext *hc) /** - * Function called with the result of our exchange lookup. + * Check whether the amount paid is sufficient to cover + * the contract. * - * @param cls the `struct PayContext` - * @param mh NULL if exchange was not found to be acceptable - * @param wire_fee current applicable fee for dealing with @a mh, NULL if not available - * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config + * @param pc payment context to check + * @return taler error code, #TALER_EC_NONE if amount is sufficient */ -static void -process_pay_with_exchange (void *cls, - struct TALER_EXCHANGE_Handle *mh, - const struct TALER_Amount *wire_fee, - int exchange_trusted) +static enum TALER_ErrorCode +check_payment_sufficient (struct PayContext *pc) { - struct PayContext *pc = cls; struct TALER_Amount acc_fee; struct TALER_Amount acc_amount; struct TALER_Amount wire_fee_delta; struct TALER_Amount wire_fee_customer_contribution; - const struct TALER_EXCHANGE_Keys *keys; - enum GNUNET_DB_QueryStatus qs; - pc->fo = NULL; - GNUNET_assert (GNUNET_YES == pc->suspended); - if (NULL == mh) - { - /* The exchange on offer is not in the set of our (trusted) - exchanges. Reject the payment. */ - GNUNET_break_op (0); - resume_pay_with_response (pc, - MHD_HTTP_PRECONDITION_FAILED, - TMH_RESPONSE_make_external_error (TALER_EC_PAY_EXCHANGE_REJECTED, - "exchange not supported")); - return; - } - pc->mh = mh; - keys = TALER_EXCHANGE_get_keys (mh); - if (NULL == keys) - { - GNUNET_break (0); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_EXCHANGE_KEYS_FAILURE, - "no keys")); - return; - } - - /* Total up the fees and the value of the deposited coins! */ GNUNET_assert (0 != pc->coins_cnt); for (unsigned int i=0;i<pc->coins_cnt;i++) { struct DepositConfirmation *dc = &pc->dc[i]; - const struct TALER_EXCHANGE_DenomPublicKey *denom_details; - denom_details = TALER_EXCHANGE_get_denomination_key (keys, - &dc->denom); - if (NULL == denom_details) - { - GNUNET_break_op (0); - resume_pay_with_response (pc, - MHD_HTTP_BAD_REQUEST, - TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, s:o}", - "error", "denomination not found", - "code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND, - "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key), - "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh))); - return; - } - if (GNUNET_OK != - TMH_AUDITORS_check_dk (mh, - denom_details, - exchange_trusted)) - { - GNUNET_break_op (0); - resume_pay_with_response (pc, - MHD_HTTP_BAD_REQUEST, - TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o}", - "error", "invalid denomination", - "code", (json_int_t) TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE, - "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key))); - return; - } - dc->deposit_fee = denom_details->fee_deposit; - dc->refund_fee = denom_details->fee_refund; + GNUNET_assert (GNUNET_YES == dc->found_in_db); if (0 == i) { - acc_fee = denom_details->fee_deposit; + acc_fee = dc->deposit_fee; acc_amount = dc->amount_with_fee; } else { if ( (GNUNET_OK != TALER_amount_add (&acc_fee, - &denom_details->fee_deposit, + &dc->deposit_fee, &acc_fee)) || (GNUNET_OK != TALER_amount_add (&acc_amount, @@ -744,11 +607,7 @@ process_pay_with_exchange (void *cls, { GNUNET_break_op (0); /* Overflow in these amounts? Very strange. */ - resume_pay_with_response (pc, - MHD_HTTP_BAD_REQUEST, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_AMOUNT_OVERFLOW, - "Overflow adding up amounts")); - return; + return TALER_EC_PAY_AMOUNT_OVERFLOW; } } if (1 == @@ -757,33 +616,22 @@ process_pay_with_exchange (void *cls, { GNUNET_break_op (0); /* fee higher than residual coin value, makes no sense. */ - resume_pay_with_response (pc, - MHD_HTTP_BAD_REQUEST, - TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, s:o}", - "hint", "fee higher than coin value", - "code", (json_int_t) TALER_EC_PAY_FEES_EXCEED_PAYMENT, - "f" /* FIXME */, TALER_JSON_from_amount (&dc->amount_with_fee), - "fee_deposit", TALER_JSON_from_amount (&denom_details->fee_deposit))); - return; + return TALER_EC_PAY_FEES_EXCEED_PAYMENT; } } /* Now compare exchange wire fee compared to what we are willing to pay */ if (GNUNET_YES != - TALER_amount_cmp_currency (wire_fee, + TALER_amount_cmp_currency (&pc->total_wire_fee, &pc->max_wire_fee)) { GNUNET_break (0); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH, - "wire_fee")); - return; + return TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH; } if (GNUNET_OK == TALER_amount_subtract (&wire_fee_delta, - wire_fee, + &pc->total_wire_fee, &pc->max_wire_fee)) { /* Actual wire fee is indeed higher than our maximum, compute @@ -795,7 +643,7 @@ process_pay_with_exchange (void *cls, else { GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (wire_fee->currency, + TALER_amount_get_zero (pc->total_wire_fee.currency, &wire_fee_customer_contribution)); } @@ -820,11 +668,7 @@ process_pay_with_exchange (void *cls, &pc->amount)) { GNUNET_break (0); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_AMOUNT_OVERFLOW, - "overflow")); - return; + return TALER_EC_PAY_AMOUNT_OVERFLOW; } /* add wire fee contribution to the total */ if (GNUNET_OK == @@ -837,11 +681,7 @@ process_pay_with_exchange (void *cls, &total_needed)) { GNUNET_break_op (0); - resume_pay_with_response (pc, - MHD_HTTP_METHOD_NOT_ACCEPTABLE, - TMH_RESPONSE_make_external_error (TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES, - "insufficient funds (including excessive exchange fees to be covered by customer)")); - return; + return TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES; } } else @@ -871,11 +711,7 @@ process_pay_with_exchange (void *cls, &wire_fee_customer_contribution)) { GNUNET_break_op (0); - resume_pay_with_response (pc, - MHD_HTTP_METHOD_NOT_ACCEPTABLE, - TMH_RESPONSE_make_external_error (TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES, - "insufficient funds (including excessive exchange fees to be covered by customer)")); - return; + return TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES; } } @@ -884,173 +720,310 @@ process_pay_with_exchange (void *cls, &pc->amount)) { GNUNET_break_op (0); - resume_pay_with_response (pc, - MHD_HTTP_METHOD_NOT_ACCEPTABLE, - TMH_RESPONSE_make_external_error (TALER_EC_PAY_PAYMENT_INSUFFICIENT, - "insufficient funds")); - return; + return TALER_EC_PAY_PAYMENT_INSUFFICIENT; } } - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Exchange and fee structure OK. Initiating deposit operation for coins\n"); + return TALER_EC_NONE; +} - if (GNUNET_OK != db->start (db->cls)) +/** + * Generate full error response based on the @a ec + * + * @param pc context for which to generate the error + * @param ec error code identifying the issue + */ +static void +generate_error_response (struct PayContext *pc, + enum TALER_ErrorCode ec) +{ + switch (ec) { + case TALER_EC_PAY_AMOUNT_OVERFLOW: + resume_pay_with_error (pc, + MHD_HTTP_BAD_REQUEST, + ec, + "Overflow adding up amounts"); + break; + case TALER_EC_PAY_FEES_EXCEED_PAYMENT: + resume_pay_with_error (pc, + MHD_HTTP_BAD_REQUEST, + ec, + "Deposit fees exceed coin's contribution"); + break; + case TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES: + resume_pay_with_error (pc, + MHD_HTTP_METHOD_NOT_ACCEPTABLE, + ec, + "insufficient funds (including excessive exchange fees to be covered by customer)"); + break; + case TALER_EC_PAY_PAYMENT_INSUFFICIENT: + resume_pay_with_error (pc, + MHD_HTTP_METHOD_NOT_ACCEPTABLE, + ec, + "insufficient funds"); + break; + case TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH: + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + ec, + "wire_fee currency does not match"); + break; + default: + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + ec, + "unexpected error code"); GNUNET_break (0); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_json_pack ("{s:s, s:I}", - "hint", "Merchant database error: could not start transaction", - "code", (json_int_t) TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR)); - return; + break; } +} - /* Check if transaction is already known, if not store it. */ + +/** + * Find the exchange we need to talk to for the next + * pending deposit permission. + * + * @param pc payment context we are processing + */ +static void +find_next_exchange (struct PayContext *pc); + + +/** + * Begin of the DB transaction. If required (from + * soft/serialization errors), the transaction can be + * restarted here. + * + * @param pc payment context to transact + */ +static void +begin_transaction (struct PayContext *pc); + + +/** + * Callback to handle a deposit permission's response. + * + * @param cls a `struct DepositConfirmation` (i.e. a pointer + * into the global array of confirmations and an index for this call + * in that array). That way, the last executed callback can detect + * that no other confirmations are on the way, and can pack a response + * for the wallet + * @param http_status HTTP response code, #MHD_HTTP_OK + * (200) for successful deposit; 0 if the exchange's reply is bogus (fails + * to follow the protocol) + * @param ec taler-specific error code, #TALER_EC_NONE on success + * @param sign_key which key did the exchange use to sign the @a proof + * @param proof the received JSON reply, + * should be kept as proof (and, in case of errors, be forwarded to + * the customer) + */ +static void +deposit_cb (void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_ExchangePublicKeyP *sign_key, + const json_t *proof) +{ + struct DepositConfirmation *dc = cls; + struct PayContext *pc = dc->pc; + enum GNUNET_DB_QueryStatus qs; + + dc->dh = NULL; + GNUNET_assert (GNUNET_YES == pc->suspended); + pc->pending_at_ce--; + if (MHD_HTTP_OK != http_status) { - struct GNUNET_HashCode h_xwire; - struct GNUNET_TIME_Absolute xtimestamp; - struct GNUNET_TIME_Absolute xrefund; - struct TALER_Amount xtotal_amount; - - qs = db->find_transaction (db->cls, - &pc->h_contract_terms, - &pc->mi->pubkey, - &h_xwire, - &xtimestamp, - &xrefund, - &xtotal_amount); - if (0 > qs) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Deposit operation failed with HTTP code %u\n", + http_status); + /* Transaction failed; stop all other ongoing deposits */ + abort_deposit (pc); + db->rollback (db->cls); + + if (NULL == proof) { - /* single, read-only SQL statements should never cause - serialization problems */ - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); - /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - /* FIXME: factor common logic of these calls into a function! */ + /* We can't do anything meaningful here, the exchange did something wrong */ resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_json_pack ("{s:I, s:s}", - "code", (json_int_t) TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, - "hint", "Merchant database error")); - return; + MHD_HTTP_SERVICE_UNAVAILABLE, + TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:I, s:s}", + "error", "exchange failed", + "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED, + "exchange-code", (json_int_t) ec, + "exchange-http-status", (json_int_t) http_status, + "hint", "The exchange provided an unexpected response")); } - if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == pc->transaction_exists) && - ( (0 != memcmp (&h_xwire, - &pc->mi->h_wire, - sizeof (struct GNUNET_HashCode))) || - (xtimestamp.abs_value_us != pc->timestamp.abs_value_us) || - (xrefund.abs_value_us != pc->refund_deadline.abs_value_us) || - (0 != TALER_amount_cmp (&xtotal_amount, - &pc->amount) ) ) ) + else { - GNUNET_break (0); - /* FIXME: factor common logic of these calls into a function! */ + /* Forward error, adding the "coin_pub" for which the + error was being generated */ + json_t *eproof; + + eproof = json_copy ((json_t *) proof); + json_object_set_new (eproof, + "coin_pub", + GNUNET_JSON_from_data_auto (&dc->coin_pub)); resume_pay_with_response (pc, - MHD_HTTP_BAD_REQUEST, - TMH_RESPONSE_make_json_pack ("{s:I, s:s}", - "code", (json_int_t) TALER_EC_PAY_DB_TRANSACTION_ID_CONFLICT, - "hint", "Transaction ID reused with different transaction details")); - return; + http_status, + TMH_RESPONSE_make_json (eproof)); + json_decref (eproof); } + return; } - - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->transaction_exists) + /* store result to DB */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Storing successful payment for h_contract_terms `%s' and merchant `%s'\n", + GNUNET_h2s (&pc->h_contract_terms), + TALER_B2S (&pc->mi->pubkey)); + /* NOTE: not run in any transaction block, simply as a + transaction by itself! */ + 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, + sign_key, + proof); + if (0 > qs) { - struct GNUNET_TIME_Absolute now; - enum GNUNET_DB_QueryStatus qs_st; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Dealing with new transaction `%s'\n", - GNUNET_h2s (&pc->h_contract_terms)); - - now = GNUNET_TIME_absolute_get (); - if (now.abs_value_us > pc->pay_deadline.abs_value_us) + /* Special report if retries insufficient */ + abort_deposit (pc); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { - /* Time expired, we don't accept this payment now! */ - const char *pd_str; - - pd_str = GNUNET_STRINGS_absolute_time_to_string (pc->pay_deadline); - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Attempt to pay coins for expired contract. Deadline: `%s'\n", - pd_str); - resume_pay_with_response (pc, - MHD_HTTP_BAD_REQUEST, - TMH_RESPONSE_make_json_pack ("{s:I, s:s}", - "code", (json_int_t) TALER_EC_PAY_OFFER_EXPIRED, - "hint", "The time to pay for this contract has expired.")); + begin_transaction (pc); return; } + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + /* Forward error including 'proof' for the body */ + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PAY_DB_STORE_PAY_ERROR, + "Merchant database error"); + return; + } + dc->found_in_db = GNUNET_YES; + pc->pending--; - qs_st = db->store_transaction (db->cls, - &pc->h_contract_terms, - &pc->mi->pubkey, - &pc->mi->h_wire, - pc->timestamp, - pc->refund_deadline, - &pc->amount); - /* Only retry if SOFT error occurred. Exit in case of OK or HARD failure */ - if (GNUNET_DB_STATUS_SOFT_ERROR == qs_st) - { - db->rollback (db->cls); - GNUNET_break (0); // FIXME: implement proper retries! - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_json_pack ("{s:I, s:s}", - "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, - "hint", "Soft merchant database error: retries not implemented")); - return; - } - /* Exit in case of HARD failure */ - if (GNUNET_DB_STATUS_HARD_ERROR == qs_st) - { - GNUNET_break (0); - db->rollback (db->cls); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_json_pack ("{s:I, s:s}", - "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, - "hint", "Merchant database error: hard error while storing transaction")); - } + if (0 != pc->pending_at_ce) + return; /* still more to do with current exchange */ + find_next_exchange (pc); +} + + +/** + * Function called with the result of our exchange lookup. + * + * @param cls the `struct PayContext` + * @param mh NULL if exchange was not found to be acceptable + * @param wire_fee current applicable fee for dealing with @a mh, NULL if not available + * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config + */ +static void +process_pay_with_exchange (void *cls, + struct TALER_EXCHANGE_Handle *mh, + const struct TALER_Amount *wire_fee, + int exchange_trusted) +{ + struct PayContext *pc = cls; + const struct TALER_EXCHANGE_Keys *keys; + + pc->fo = NULL; + GNUNET_assert (GNUNET_YES == pc->suspended); + if (NULL == mh) + { + /* The exchange on offer is not in the set of our (trusted) + exchanges. Reject the payment. */ + GNUNET_break_op (0); + resume_pay_with_error (pc, + MHD_HTTP_PRECONDITION_FAILED, + TALER_EC_PAY_EXCHANGE_REJECTED, + "exchange not supported"); + return; + } + if (GNUNET_OK != + TALER_amount_add (&pc->total_wire_fee, + &pc->total_wire_fee, + wire_fee)) + { + GNUNET_break_op (0); + resume_pay_with_error (pc, + MHD_HTTP_PRECONDITION_FAILED, + TALER_EC_PAY_EXCHANGE_REJECTED, + "exchange charges incompatible wire fee"); + return; + } + pc->mh = mh; + keys = TALER_EXCHANGE_get_keys (mh); + if (NULL == keys) + { + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PAY_EXCHANGE_KEYS_FAILURE, + "no keys"); + return; + } - /** - * Break if we couldn't modify one, and only one line; this - * includes hard errors. - */ - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs_st) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected query status %d while storing /pay transaction!\n", - (int) qs_st); - resume_pay_with_response (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_json_pack ("{s:I, s:s}", - "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, - "hint", "Merchant database error: failed to store transaction")); - return; - } - } /* end of if (GNUNET_NO == pc->transaction_exists) */ 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 */ + /* Initiate /deposit operation for all coins of + the current exchange (!) */ + GNUNET_assert (0 == pc->pending_at_ce); for (unsigned int i=0;i<pc->coins_cnt;i++) { struct DepositConfirmation *dc = &pc->dc[i]; + const struct TALER_EXCHANGE_DenomPublicKey *denom_details; if (GNUNET_YES == 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); + if (NULL == denom_details) + { + GNUNET_break_op (0); + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, s:o}", + "error", "denomination not found", + "code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND, + "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key), + "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh))); + return; + } + if (GNUNET_OK != + TMH_AUDITORS_check_dk (mh, + denom_details, + exchange_trusted)) + { + GNUNET_break_op (0); + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o}", + "error", "invalid denomination", + "code", (json_int_t) TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE, + "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_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->mi->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); - dc->dh = TALER_EXCHANGE_deposit (mh, &dc->amount_with_fee, pc->wire_transfer_deadline, @@ -1070,7 +1043,6 @@ process_pay_with_exchange (void *cls, /* Signature was invalid. If the exchange was unavailable, * we'd get that information in the callback. */ GNUNET_break_op (0); - db->rollback (db->cls); resume_pay_with_response (pc, MHD_HTTP_UNAUTHORIZED, TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:i}", @@ -1080,7 +1052,47 @@ process_pay_with_exchange (void *cls, "coin_idx", i)); return; } + pc->pending_at_ce++; + } +} + + +/** + * Find the exchange we need to talk to for the next + * pending deposit permission. + * + * @param pc payment context we are processing + */ +static void +find_next_exchange (struct PayContext *pc) +{ + for (unsigned int i=0;i<pc->coins_cnt;i++) + { + struct DepositConfirmation *dc = &pc->dc[i]; + + if (GNUNET_YES != dc->found_in_db) + { + pc->current_exchange = dc->exchange_url; + pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange, + pc->mi->wire_method, + &process_pay_with_exchange, + pc); + if (NULL == pc->fo) + { + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PAY_EXCHANGE_FAILED, + "Failed to lookup exchange by URL"); + return; + } + return; + } } + pc->current_exchange = NULL; + /* We are done with all the HTTP requests, go back and try + the 'big' database transaction! (It should work now!) */ + begin_transaction (pc); } @@ -1103,10 +1115,10 @@ handle_pay_timeout (void *cls) TMH_EXCHANGES_find_exchange_cancel (pc->fo); pc->fo = NULL; } - resume_pay_with_response (pc, - MHD_HTTP_SERVICE_UNAVAILABLE, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_EXCHANGE_TIMEOUT, - "exchange not reachable")); + resume_pay_with_error (pc, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_EC_PAY_EXCHANGE_TIMEOUT, + "exchange not reachable"); } @@ -1130,6 +1142,7 @@ check_coin_paid (void *cls, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_Amount *refund_fee, + // FIXME: also store AND fetch wire fee! const json_t *exchange_proof) { struct PayContext *pc = cls; @@ -1144,6 +1157,9 @@ check_coin_paid (void *cls, for (unsigned int i=0;i<pc->coins_cnt;i++) { struct DepositConfirmation *dc = &pc->dc[i]; + + if (GNUNET_YES == dc->found_in_db) + continue; /* processed earlier */ /* Get matching coin from results*/ if ( (0 != memcmp (coin_pub, &dc->coin_pub, @@ -1151,16 +1167,35 @@ check_coin_paid (void *cls, (0 != TALER_amount_cmp (amount_with_fee, &dc->amount_with_fee)) ) continue; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Coin (%s) already found in our DB.\n", - TALER_b2s (coin_pub, sizeof (*coin_pub))); - + TALER_b2s (coin_pub, + sizeof (*coin_pub))); + if (GNUNET_OK != + 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 (GNUNET_OK != + TALER_amount_add (&pc->total_fees_paid, + &pc->total_fees_paid, + deposit_fee)) + { + /* We accepted this coin for payment on this contract before, + and now we can't even add the amount!? */ + GNUNET_break (0); + continue; + } + dc->deposit_fee = *deposit_fee; + dc->refund_fee = *refund_fee; + // dc->wire_fee = *wire_fee; // TBD... + dc->amount_with_fee = *amount_with_fee; dc->found_in_db = GNUNET_YES; - /** - * What happens if a (mad) wallet sends new coins on a - * contract that it already paid for? - */ pc->pending--; } } @@ -1187,15 +1222,16 @@ parse_pay (struct MHD_Connection *connection, json_t *coin; json_t *merchant; unsigned int coins_index; - const char *chosen_exchange; const char *order_id; struct TALER_MerchantPublicKeyP merchant_pub; int res; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("coins", &coins), - GNUNET_JSON_spec_string ("exchange", &chosen_exchange), - GNUNET_JSON_spec_string ("order_id", &order_id), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + 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; @@ -1295,7 +1331,6 @@ parse_pay (struct MHD_Connection *connection, GNUNET_STRINGS_data_to_string_alloc (&pc->mi->pubkey, sizeof (pc->mi->pubkey))); - pc->chosen_exchange = GNUNET_strdup (chosen_exchange); { struct GNUNET_JSON_Specification espec[] = { GNUNET_JSON_spec_absolute_time ("refund_deadline", @@ -1379,6 +1414,10 @@ parse_pay (struct MHD_Connection *connection, TALER_amount_get_zero (pc->max_fee.currency, &pc->max_wire_fee)); } + /* Initialize wire fee total */ + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (pc->max_fee.currency, + &pc->total_wire_fee)); if (NULL != json_object_get (pc->contract_terms, "wire_fee_amortization")) { @@ -1420,12 +1459,20 @@ parse_pay (struct MHD_Connection *connection, json_array_foreach (coins, coins_index, coin) { struct DepositConfirmation *dc = &pc->dc[coins_index]; + const char *exchange_url; struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_denomination_public_key ("denom_pub", &dc->denom), - TALER_JSON_spec_amount ("f" /* FIXME */, &dc->amount_with_fee), - 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), + 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() }; @@ -1436,9 +1483,9 @@ parse_pay (struct MHD_Connection *connection, { GNUNET_JSON_parse_free (spec); GNUNET_break_op (0); - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + return res; } - + dc->exchange_url = GNUNET_strdup (exchange_url); dc->index = coins_index; dc->pc = pc; } @@ -1449,29 +1496,80 @@ parse_pay (struct MHD_Connection *connection, /** - * Process a payment for a proposal. + * 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 value to return to MHD (#MHD_NO to drop connection, - * #MHD_YES to keep handling it) + * @param cls closure with a `struct PayContext` + * @param coin_pub public coin from which the refund comes from + * @param rtransaction_id identificator of the refund + * @param reason human-readable explaination of the refund + * @param refund_amount refund amount which is being taken from coin_pub + * @param refund_fee cost of this refund operation */ -static int -handler_pay_json (struct MHD_Connection *connection, - const json_t *root, - struct PayContext *pc) +static void +check_coin_refunded (void *cls, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const char *reason, + const struct TALER_Amount *refund_amount, + const struct TALER_Amount *refund_fee) { - int ret; + struct PayContext *pc = cls; + + /* FIXME: to be implemented (#5158) */ + (void) pc; +} + + +/** + * Begin of the DB transaction. If required (from + * soft/serialization errors), the transaction can be + * restarted here. + * + * @param pc payment context to transact + */ +static void +begin_transaction (struct PayContext *pc) +{ enum GNUNET_DB_QueryStatus qs; - ret = parse_pay (connection, - root, - pc); - if (GNUNET_OK != ret) - return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + /* Avoid re-trying transactions on soft errors forever! */ + if (pc->retry_counter++ > MAX_RETRIES) + { + GNUNET_break (0); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, + "hint", "Soft merchant database error: retry counter exceeded")); + return; + } + + GNUNET_assert (GNUNET_YES == pc->suspended); - /* Check if this payment attempt has already succeeded */ + /* First, try to see if we have all we need already done */ + if (GNUNET_OK != db->start (db->cls)) + { + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, + "Merchant database error (could not start transaction)"); + return; + } + GNUNET_break (GNUNET_OK == + TALER_amount_get_zero (pc->amount.currency, + &pc->total_paid)); + GNUNET_break (GNUNET_OK == + TALER_amount_get_zero (pc->amount.currency, + &pc->total_fees_paid)); + GNUNET_break (GNUNET_OK == + TALER_amount_get_zero (pc->amount.currency, + &pc->total_refunded)); + + /* Check if some of these coins already succeeded */ qs = db->find_payments (db->cls, &pc->h_contract_terms, &pc->mi->pubkey, @@ -1479,50 +1577,269 @@ handler_pay_json (struct MHD_Connection *connection, pc); if (0 > qs) { - /* single, read-only SQL statements should never cause - serialization problems */ - GNUNET_break (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); - return TMH_RESPONSE_reply_internal_error (connection, - TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, - "Merchant database error"); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, + "Merchant database error"); + return; + } + + /* Check if we refunded some of the coins */ + qs = db->get_refunds_from_contract_terms_hash (db->cls, + &pc->mi->pubkey, + &pc->h_contract_terms, + &check_coin_refunded, + pc); + 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_FETCH_TRANSACTION_ERROR, + "Merchant database error"); + return; } + + /* FIXME: check if wallet is going for a refund, + (on aborted operation), or for a payment! #5158 */ + + + /* Final termination case: all coins already known, just + generate ultimate outcome. */ if (0 == pc->pending) { - struct MHD_Response *resp; - int ret; + enum TALER_ErrorCode ec; + + ec = check_payment_sufficient (pc); + if (TALER_EC_NONE == ec) + { + /* Payment succeeded, commit! */ + qs = db->mark_proposal_paid (db->cls, + &pc->h_contract_terms, + &pc->mi->pubkey); + if (0 <= qs) + qs = db->commit (db->cls); + if (0 > qs) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + begin_transaction (pc); + return; + } + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR, + "Merchant database error: could not mark proposal as 'paid'"); + return; + } + resume_pay_with_response (pc, + MHD_HTTP_OK, + sign_success_response (pc)); + return; + } + generate_error_response (pc, + ec); + return; + } + + + /* Check if transaction is already known, if not store it. */ + { + struct GNUNET_HashCode h_xwire; + struct GNUNET_TIME_Absolute xtimestamp; + struct GNUNET_TIME_Absolute xrefund; + struct TALER_Amount xtotal_amount; + + qs = db->find_transaction (db->cls, + &pc->h_contract_terms, + &pc->mi->pubkey, + &h_xwire, + &xtimestamp, + &xrefund, + &xtotal_amount); + if (0 > qs) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + db->rollback (db->cls); + begin_transaction (pc); + return; + } + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + db->rollback (db->cls); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, + "hint", "Merchant database error")); + return; + } + if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && + ( (0 != memcmp (&h_xwire, + &pc->mi->h_wire, + sizeof (struct GNUNET_HashCode))) || + (xtimestamp.abs_value_us != pc->timestamp.abs_value_us) || + (xrefund.abs_value_us != pc->refund_deadline.abs_value_us) || + (0 != TALER_amount_cmp (&xtotal_amount, + &pc->amount) ) ) ) + { + GNUNET_break (0); + db->rollback (db->cls); + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) TALER_EC_PAY_DB_TRANSACTION_ID_CONFLICT, + "hint", "Transaction ID reused with different transaction details")); + return; + } + } + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + struct GNUNET_TIME_Absolute now; + enum GNUNET_DB_QueryStatus qs_st; - /* Payment succeeded in the past; take short cut - and accept immediately */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Payment succeeded in the past; taking short cut"); - resp = sign_success_response (pc); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - MHD_destroy_response (resp); - return ret; + "Dealing with new transaction `%s'\n", + GNUNET_h2s (&pc->h_contract_terms)); + + now = GNUNET_TIME_absolute_get (); + if (now.abs_value_us > pc->pay_deadline.abs_value_us) + { + /* Time expired, we don't accept this payment now! */ + const char *pd_str; + + pd_str = GNUNET_STRINGS_absolute_time_to_string (pc->pay_deadline); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Attempt to pay coins for expired contract. Deadline: `%s'\n", + pd_str); + db->rollback (db->cls); + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) TALER_EC_PAY_OFFER_EXPIRED, + "hint", "The time to pay for this contract has expired.")); + return; + } + + qs_st = db->store_transaction (db->cls, + &pc->h_contract_terms, + &pc->mi->pubkey, + &pc->mi->h_wire, + pc->timestamp, + pc->refund_deadline, + &pc->amount); + /* Only retry if SOFT error occurred. Exit in case of OK or HARD failure */ + if (GNUNET_DB_STATUS_SOFT_ERROR == qs_st) + { + db->rollback (db->cls); + begin_transaction (pc); + return; + } + /* Exit in case of HARD failure */ + if (GNUNET_DB_STATUS_HARD_ERROR == qs_st) + { + GNUNET_break (0); + db->rollback (db->cls); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, + "hint", "Merchant database error: hard error while storing transaction")); + } + + /** + * Break if we couldn't modify one, and only one line; this + * includes hard errors. + */ + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs_st) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected query status %d while storing /pay transaction!\n", + (int) qs_st); + db->rollback (db->cls); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:I, s:s}", + "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, + "hint", "Merchant database error: failed to store transaction")); + return; + } + + qs = db->commit (db->cls); + if (0 > qs) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + db->rollback (db->cls); + 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"); + return; + } + } /* end of if (GNUNET_NO == qs) */ + else + { + /* transaction record already existed, we made no DB changes, + so we can just rollback */ + db->rollback (db->cls); } + + /* Ok, we need to first go to the network. + Do that interaction in *tiny* transactions. */ + find_next_exchange (pc); +} - MHD_suspend_connection (connection); - pc->suspended = GNUNET_YES; - /* Find the responsible exchange, this may take a while... */ - pc->fo = TMH_EXCHANGES_find_exchange (pc->chosen_exchange, - pc->mi->wire_method, - &process_pay_with_exchange, - pc); +/** + * Process a payment for a proposal. + * + * @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) + */ +static int +handler_pay_json (struct MHD_Connection *connection, + const json_t *root, + struct PayContext *pc) +{ + int ret; - /* ... so we suspend connection until the last coin has been ack'd - or until we have encountered a hard error. Eventually, we will - resume the connection and send back a response using - #resume_pay_with_response(). */ + ret = parse_pay (connection, + root, + pc); + if (GNUNET_OK != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + MHD_suspend_connection (connection); + pc->suspended = GNUNET_YES; 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; } @@ -1587,13 +1904,7 @@ MH_handler_pay (struct TMH_RequestHandler *rh, res ? "OK" : "FAILED"); return res; } - if (NULL != pc->chosen_exchange) - { - // FIXME: explain in comment why this could happen! - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Shouldn't be here. Old MHD version?\n"); - return MHD_YES; - } + res = TMH_PARSE_post_json (connection, &pc->json_parse_context, upload_data, diff --git a/src/backend/taler-merchant-httpd_responses.c b/src/backend/taler-merchant-httpd_responses.c index 1417fa24..71d04064 100644 --- a/src/backend/taler-merchant-httpd_responses.c +++ b/src/backend/taler-merchant-httpd_responses.c @@ -181,11 +181,10 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, * @return a MHD response object */ struct MHD_Response * -TMH_RESPONSE_make_internal_error (enum TALER_ErrorCode ec, - const char *hint) +TMH_RESPONSE_make_error (enum TALER_ErrorCode ec, + const char *hint) { - return TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:s}", - "error", "internal error", + return TMH_RESPONSE_make_json_pack ("{s:I, s:s}", "code", (json_int_t) ec, "hint", hint); } @@ -206,8 +205,7 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, { return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - "{s:s, s:I, s:s}", - "error", "internal error", + "{s:I, s:s}", "code", (json_int_t) ec, "hint", hint); } @@ -354,32 +352,13 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, { return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, - "{s:s, s:I, s:s}", - "error", "client error", + "{s:I, s:s}", "code", (json_int_t) ec, "hint", hint); } /** - * Create a response indicating an external error. - * - * @param ec error code to return - * @param hint hint about the internal error's nature - * @return a MHD response object - */ -struct MHD_Response * -TMH_RESPONSE_make_external_error (enum TALER_ErrorCode ec, - const char *hint) -{ - return TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:s}", - "error", "client error", - "code", (json_int_t) ec, - "hint", hint); -} - - -/** * Send a response indicating a missing argument. * * @param connection the MHD connection to use @@ -394,7 +373,7 @@ TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, { return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, - "{ s:s, s:I, s:s}", + "{s:s, s:I, s:s}", "error", "missing parameter", "code", (json_int_t) ec, "parameter", param_name); diff --git a/src/backend/taler-merchant-httpd_responses.h b/src/backend/taler-merchant-httpd_responses.h index a96b696d..3dbd0049 100644 --- a/src/backend/taler-merchant-httpd_responses.h +++ b/src/backend/taler-merchant-httpd_responses.h @@ -143,20 +143,6 @@ TMH_RESPONSE_reply_bad_request (struct MHD_Connection *connection, /** - * Send a response indicating an internal error. - * - * @param connection the MHD connection to use - * @param ec error code to return - * @param hint hint about the internal error's nature - * @return a MHD result code - */ -int -TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, - enum TALER_ErrorCode ec, - const char *hint); - - -/** * Create a response indicating an internal error. * * @param ec error code to return @@ -183,15 +169,29 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, /** - * Create a response indicating an external error. + * Send a response indicating an internal error. + * + * @param connection the MHD connection to use + * @param ec error code to return + * @param hint hint about the internal error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, + enum TALER_ErrorCode ec, + const char *hint); + + +/** + * Create a response indicating an error. * * @param ec error code to return * @param hint hint about the internal error's nature * @return a MHD response object */ struct MHD_Response * -TMH_RESPONSE_make_external_error (enum TALER_ErrorCode ec, - const char *hint); +TMH_RESPONSE_make_error (enum TALER_ErrorCode ec, + const char *hint); /** diff --git a/src/backend/taler-merchant-httpd_track-transaction.c b/src/backend/taler-merchant-httpd_track-transaction.c index c60daf61..27d2d06e 100644 --- a/src/backend/taler-merchant-httpd_track-transaction.c +++ b/src/backend/taler-merchant-httpd_track-transaction.c @@ -640,8 +640,8 @@ wtid_cb (void *cls, resume_track_transaction_with_response (tcc->tctx, MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSACTION_DB_FETCH_FAILED, - "Fail to query database about proofs")); + TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSACTION_DB_FETCH_FAILED, + "Fail to query database about proofs")); return; } /* WARNING: if two transactions got aggregated under the same @@ -878,7 +878,7 @@ handle_track_transaction_timeout (void *cls) } resume_track_transaction_with_response (tctx, MHD_HTTP_SERVICE_UNAVAILABLE, - TMH_RESPONSE_make_internal_error (TALER_EC_PAY_EXCHANGE_TIMEOUT, + TMH_RESPONSE_make_error (TALER_EC_PAY_EXCHANGE_TIMEOUT, "exchange not reachable")); } diff --git a/src/backend/taler-merchant-httpd_track-transfer.c b/src/backend/taler-merchant-httpd_track-transfer.c index 0e84b1d7..3d4b4184 100644 --- a/src/backend/taler-merchant-httpd_track-transfer.c +++ b/src/backend/taler-merchant-httpd_track-transfer.c @@ -750,8 +750,8 @@ wire_transfer_cb (void *cls, resume_track_transfer_with_response (rctx, MHD_HTTP_INTERNAL_SERVER_ERROR, - TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, - "Fail to elaborate the response.")); + TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, + "Fail to elaborate the response.")); return; } @@ -818,7 +818,7 @@ handle_track_transfer_timeout (void *cls) } resume_track_transfer_with_response (rctx, MHD_HTTP_SERVICE_UNAVAILABLE, - TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT, + TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT, "exchange not reachable")); } @@ -845,8 +845,8 @@ proof_cb (void *cls, { rctx->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; rctx->response - = TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, - "Fail to elaborate response."); + = TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR, + "Fail to elaborate response."); return; } diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index d6b32602..a3fca0fe 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -55,7 +55,7 @@ typedef void * Does a GET /refund. * * @param ctx execution context - * @param backend_uri base URL of the merchant backend + * @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 @@ -63,7 +63,7 @@ typedef void */ struct TALER_MERCHANT_RefundLookupOperation * TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const char *order_id, const char *instance, TALER_MERCHANT_RefundLookupCallback cb, @@ -103,7 +103,7 @@ typedef void * Increase the refund associated to a order * * @param ctx the CURL context used to connect to the backend - * @param backend_uri backend's base URL, including final "/" + * @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 @@ -113,7 +113,7 @@ typedef void */ struct TALER_MERCHANT_RefundIncreaseOperation * TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const char *order_id, const struct TALER_Amount *refund, const char *reason, @@ -165,7 +165,7 @@ typedef void * PUT an order to the backend and receives the related proposal. * * @param ctx execution context - * @param backend_uri URI of the backend + * @param backend_url URL of the backend * @param order basic information about this purchase, to be extended by the * backend * @param proposal_cb the callback to call when a reply for this request is available @@ -174,7 +174,7 @@ typedef void */ struct TALER_MERCHANT_ProposalOperation * TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const json_t *order, TALER_MERCHANT_ProposalCallback proposal_cb, void *proposal_cb_cls); @@ -215,7 +215,7 @@ typedef void * retrieve a proposal data by providing its transaction id. * * @param ctx execution context - * @param backend_uri base URL of the merchant backend + * @param backend_url base URL of the merchant backend * @param transaction_id transaction id used to perform the lookup * @param plo_cb callback which will work the response gotten from the backend * @param plo_cb_cls closure to pass to @a history_cb @@ -223,7 +223,7 @@ typedef void */ struct TALER_MERCHANT_ProposalLookupOperation * TALER_MERCHANT_proposal_lookup (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const char *transaction_id, const char *instance, TALER_MERCHANT_ProposalLookupOperationCallback plo_cb, @@ -249,7 +249,7 @@ TALER_MERCHANT_proposal_lookup_cancel (struct TALER_MERCHANT_ProposalLookupOpera * 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 URI on + * 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; @@ -310,6 +310,11 @@ struct TALER_MERCHANT_PayCoin */ struct TALER_Amount amount_without_fee; + /** + * URL of the exchange that issued @e coin_priv. + */ + const char *exchange_url; + }; @@ -317,7 +322,7 @@ struct TALER_MERCHANT_PayCoin * Pay a merchant. API for wallets that have the coin's private keys. * * @param ctx execution context - * @param merchant_uri base URI of the merchant + * @param merchant_url base URL of the merchant * @param instance which merchant instance will receive this payment * @param h_wire hash of the merchant’s account details * @param h_contract hash of the contact of the merchant with the customer @@ -329,7 +334,6 @@ struct TALER_MERCHANT_PayCoin * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) * @param pay_deadline maximum time limit to pay for this contract - * @param exchange_uri URI of the exchange that the coins belong to * @param num_coins number of coins used to pay * @param coins array of coins we use to pay * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. @@ -339,7 +343,7 @@ struct TALER_MERCHANT_PayCoin */ struct TALER_MERCHANT_Pay * TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, - const char *merchant_uri, + const char *merchant_url, const char *instance, const struct GNUNET_HashCode *h_contract, const struct TALER_Amount *amount, @@ -350,7 +354,6 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, struct GNUNET_TIME_Absolute refund_deadline, struct GNUNET_TIME_Absolute pay_deadline, const struct GNUNET_HashCode *h_wire, - const char *exchange_uri, const char *order_id, unsigned int num_coins, const struct TALER_MERCHANT_PayCoin *coins, @@ -399,6 +402,11 @@ struct TALER_MERCHANT_PaidCoin */ struct TALER_Amount amount_without_fee; + /** + * What is the URL of the exchange that issued @a coin_pub? + */ + const char *exchange_url; + }; @@ -409,7 +417,7 @@ struct TALER_MERCHANT_PaidCoin * in the type of @a coins compared to #TALER_MERCHANT_pay(). * * @param ctx execution context - * @param merchant_uri base URI of the merchant + * @param merchant_url base URL of the merchant * @param instance which merchant instance will receive this payment * @param h_contract hash of the contact of the merchant with the customer * @param amount total value of the contract to be paid to the merchant @@ -420,7 +428,6 @@ struct TALER_MERCHANT_PaidCoin * @param pay_deadline maximum time limit to pay for this contract * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant * @param wire_transfer_deadline date by which the merchant would like the exchange to execute the wire transfer (can be zero if there is no specific date desired by the frontend). If non-zero, must be larger than @a refund_deadline. - * @param exchange_uri URI of the exchange that the coins belong to * @param num_coins number of coins used to pay * @param coins array of coins we use to pay * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. @@ -430,10 +437,9 @@ struct TALER_MERCHANT_PaidCoin */ struct TALER_MERCHANT_Pay * TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, - const char *merchant_uri, + const char *merchant_url, const struct TALER_MerchantPublicKeyP *merchant_pub, const char *order_id, - const char *exchange_uri, unsigned int num_coins, const struct TALER_MERCHANT_PaidCoin *coins, TALER_MERCHANT_PayCallback pay_cb, @@ -517,7 +523,7 @@ typedef void * Request backend to return deposits associated with a given wtid. * * @param ctx execution context - * @param backend_uri base URI of the backend + * @param backend_url base URL of the backend * @param instance which merchant instance is going to be tracked * @param wire_method wire method used for the wire transfer * @param wtid base32 string indicating a wtid @@ -528,11 +534,11 @@ typedef void */ struct TALER_MERCHANT_TrackTransferHandle * TALER_MERCHANT_track_transfer (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const char *instance, const char *wire_method, const struct TALER_WireTransferIdentifierRawP *wtid, - const char *exchange_uri, + const char *exchange_url, TALER_MERCHANT_TrackTransferCallback track_transfer_cb, void *track_transfer_cb_cls); @@ -601,7 +607,7 @@ typedef void * Request backend to return deposits associated with a given wtid. * * @param ctx execution context - * @param backend_uri base URI of the backend + * @param backend_url base URL of the backend * @param instance which merchant instance is going to be tracked * @param transaction_id which transaction should we trace * @param track_transaction_cb the callback to call when a reply for this request is available @@ -610,7 +616,7 @@ typedef void */ struct TALER_MERCHANT_TrackTransactionHandle * TALER_MERCHANT_track_transaction (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const char *instance, const char *order_id, TALER_MERCHANT_TrackTransactionCallback track_transaction_cb, @@ -650,7 +656,7 @@ typedef void * Issue a /history request to the backend. * * @param ctx execution context - * @param backend_uri base URL of the merchant backend + * @param backend_url base URL of the merchant backend * @param instance which merchant instance is performing this call * @param start return @a delta records starting from position @a start * @param delta return @a delta records starting from position @a start @@ -661,7 +667,7 @@ typedef void */ struct TALER_MERCHANT_HistoryOperation * TALER_MERCHANT_history (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const char *instance, unsigned int start, unsigned int delta, @@ -707,7 +713,7 @@ typedef void * Issue a /tip-enable request to the backend. Informs the backend * that a reserve is now available for tipping. Note that the * respective @a reserve_priv must also be bound to one or more - * instances (together with the URI of the exchange) via the backend's + * instances (together with the URL of the exchange) via the backend's * configuration file before it can be used. Usually, the process * is that one first configures an exchange and a @a reserve_priv for * an instance, and then enables (or re-enables) the reserve by @@ -715,7 +721,7 @@ typedef void * this API. * * @param ctx execution context - * @param backend_uri base URL of the merchant backend + * @param backend_url base URL of the merchant backend * @param amount amount that was credited to the reserve * @param expiration when will the reserve expire * @param reserve_priv private key of the reserve @@ -726,7 +732,7 @@ typedef void */ struct TALER_MERCHANT_TipEnableOperation * TALER_MERCHANT_tip_enable (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const struct TALER_Amount *amount, struct GNUNET_TIME_Absolute expiration, const struct TALER_ReservePrivateKeyP *reserve_priv, @@ -762,7 +768,7 @@ struct TALER_MERCHANT_TipAuthorizeOperation; * @param ec taler-specific error code * @param tip_id which tip ID should be used to pickup the tip * @param tip_expiration when does the tip expire (needs to be picked up before this time) - * @param exchange_uri at what exchange can the tip be picked up + * @param exchange_url at what exchange can the tip be picked up */ typedef void (*TALER_MERCHANT_TipAuthorizeCallback) (void *cls, @@ -770,7 +776,7 @@ typedef void enum TALER_ErrorCode ec, const struct GNUNET_HashCode *tip_id, struct GNUNET_TIME_Absolute tip_expiration, - const char *exchange_uri); + const char *exchange_url); /** @@ -845,7 +851,7 @@ typedef void * that a customer wants to pick up a tip. * * @param ctx execution context - * @param backend_uri base URL of the merchant backend + * @param backend_url base URL of the merchant backend * @param tip_id unique identifier for the tip * @param num_planches number of planchets provided in @a planchets * @param planchets array of planchets to be signed into existence for the tip @@ -855,7 +861,7 @@ typedef void */ struct TALER_MERCHANT_TipPickupOperation * TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx, - const char *backend_uri, + const char *backend_url, const struct GNUNET_HashCode *tip_id, unsigned int num_planchets, struct TALER_PlanchetDetail *planchets, diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c index a4800617..bfb1ae85 100644 --- a/src/lib/merchant_api_pay.c +++ b/src/lib/merchant_api_pay.c @@ -265,7 +265,6 @@ handle_pay_finished (void *cls, * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) * @param pay_deadline maximum time limit to pay for this contract * @param h_wire hash of the merchant’s account details - * @param exchange_uri URI of the exchange that the coins belong to * @param order_id order id of the proposal being paid * @param num_coins number of coins used to pay * @param coins array of coins we use to pay @@ -286,14 +285,12 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, struct GNUNET_TIME_Absolute refund_deadline, struct GNUNET_TIME_Absolute pay_deadline, const struct GNUNET_HashCode *h_wire, - const char *exchange_uri, const char *order_id, unsigned int num_coins, const struct TALER_MERCHANT_PayCoin *coins, TALER_MERCHANT_PayCallback pay_cb, void *pay_cb_cls) { - unsigned int i; struct TALER_DepositRequestPS dr; struct TALER_MERCHANT_PaidCoin pc[num_coins]; @@ -316,7 +313,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); dr.merchant = *merchant_pub; - for (i=0;i<num_coins;i++) + for (unsigned int i=0;i<num_coins;i++) { const struct TALER_MERCHANT_PayCoin *coin = &coins[i]; struct TALER_MERCHANT_PaidCoin *p = &pc[i]; @@ -355,12 +352,12 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, p->coin_pub = dr.coin_pub; p->amount_with_fee = coin->amount_with_fee; p->amount_without_fee = coin->amount_without_fee; + p->exchange_url = coin->exchange_url; } return TALER_MERCHANT_pay_frontend (ctx, merchant_uri, merchant_pub, order_id, - exchange_uri, num_coins, pc, pay_cb, @@ -377,7 +374,6 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, * @param ctx the execution loop context * @param merchant_uri base URI of the merchant's backend * @param merchant_pub public key of the merchant - * @param exchange_uri URI of the exchange that the coins belong to * @param num_coins number of coins used to pay * @param coins array of coins we use to pay * @param pay_cb the callback to call when a reply for this request is available @@ -389,7 +385,6 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, const char *merchant_uri, const struct TALER_MerchantPublicKeyP *merchant_pub, const char *order_id, - const char *exchange_uri, unsigned int num_coins, const struct TALER_MERCHANT_PaidCoin *coins, TALER_MERCHANT_PayCallback pay_cb, @@ -449,11 +444,12 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, } /* create JSON for this coin */ - j_coin = json_pack ("{s:o, s:o," /* f/coin_pub */ - " s:o, s:o," /* denom_pub / ub_sig */ - " s:o}", /* coin_sig */ - "f", TALER_JSON_from_amount (&pc->amount_with_fee), + j_coin = json_pack ("{s:o, s:o," /* contribution/coin_pub */ + " s:s, s:o," /* exchange_url / denom_pub */ + " s:o, s:o}", /* ub_sig / coin_sig */ + "contribution", TALER_JSON_from_amount (&pc->amount_with_fee), "coin_pub", GNUNET_JSON_from_data_auto (&pc->coin_pub), + "exchange_url", pc->exchange_url, "denom_pub", GNUNET_JSON_from_rsa_public_key (pc->denom_pub.rsa_public_key), "ub_sig", GNUNET_JSON_from_rsa_signature (pc->denom_sig.rsa_signature), "coin_sig", GNUNET_JSON_from_data_auto (&pc->coin_sig) @@ -469,12 +465,10 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, } pay_obj = json_pack ("{" - " s:s," /* exchange */ " s:o," /* coins */ " s:s," /* order_id */ " s:o," /* merchant_pub */ "}", - "exchange", exchange_uri, "coins", j_coins, "order_id", order_id, "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub)); diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c index 769421b5..941f46b1 100644 --- a/src/lib/test_merchant_api.c +++ b/src/lib/test_merchant_api.c @@ -2696,12 +2696,14 @@ interpreter_run (void *cls) icoin->denom_pub = coin_ref->details.reserve_withdraw.pk->key; icoin->denom_sig = coin_ref->details.reserve_withdraw.sig; icoin->denom_value = coin_ref->details.reserve_withdraw.pk->value; + icoin->exchange_url = EXCHANGE_URL; break; case OC_TIP_PICKUP: icoin->coin_priv = coin_ref->details.tip_pickup.psa[ci].coin_priv; icoin->denom_pub = coin_ref->details.tip_pickup.dks[ci]->key; icoin->denom_sig = coin_ref->details.tip_pickup.sigs[ci]; icoin->denom_value = coin_ref->details.tip_pickup.dks[ci]->value; + icoin->exchange_url = EXCHANGE_URL; break; default: GNUNET_assert (0); @@ -2729,7 +2731,6 @@ interpreter_run (void *cls) refund_deadline, pay_deadline, &h_wire, - EXCHANGE_URL, order_id, npc /* num_coins */, pc /* coins */, diff --git a/src/merchant-tools/taler-merchant-generate-payments.c b/src/merchant-tools/taler-merchant-generate-payments.c index 4ba257fe..887b9db4 100644 --- a/src/merchant-tools/taler-merchant-generate-payments.c +++ b/src/merchant-tools/taler-merchant-generate-payments.c @@ -1167,6 +1167,7 @@ interpreter_run (void *cls) pc.denom_pub = coin_ref->details.reserve_withdraw.pk->key; pc.denom_sig = coin_ref->details.reserve_withdraw.sig; pc.denom_value = coin_ref->details.reserve_withdraw.pk->value; + pc.exchange_url = exchange_url; break; default: GNUNET_assert (0); @@ -1209,7 +1210,6 @@ interpreter_run (void *cls) refund_deadline, pay_deadline, &h_wire, - exchange_url, order_id, 1 /* num_coins */, &pc /* coins */, |