merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 0b945df357bf820540b67b412d47aa9073797267
parent 831272d4344151a62d2a8ffc4188d4302f64be5b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri, 10 Apr 2020 20:14:57 +0200

implement #5299

Diffstat:
Msrc/backend/taler-merchant-httpd.c | 1+
Msrc/backend/taler-merchant-httpd_check-payment.c | 4+++-
Msrc/backend/taler-merchant-httpd_order.c | 8+++++---
Msrc/backend/taler-merchant-httpd_pay.c | 35+++++++++++++++++++----------------
Msrc/backend/taler-merchant-httpd_poll-payment.c | 4+++-
Msrc/backend/taler-merchant-httpd_refund.c | 5++++-
Msrc/backend/taler-merchant-httpd_refund.h | 8++++++++
Msrc/backend/taler-merchant-httpd_refund_lookup.c | 619++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/backend/taler-merchant-httpd_tip-reserve-helper.c | 12++++++------
Msrc/backend/taler-merchant-httpd_track-transaction.c | 18+++++++++---------
Msrc/backend/taler-merchant-httpd_track-transfer.c | 12++++++------
Msrc/backenddb/merchant-0001.sql | 38+++++++++++++++++++-------------------
Msrc/backenddb/plugin_merchantdb_postgres.c | 75+++++++++++++++++++++++++++++++++------------------------------------------
Msrc/backenddb/test_merchantdb.c | 4+++-
Msrc/include/taler_merchant_service.h | 57++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/include/taler_merchantdb_plugin.h | 4+++-
Msrc/lib/merchant_api_common.c | 14+++++++-------
Msrc/lib/merchant_api_pay.c | 2+-
Msrc/lib/merchant_api_refund.c | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/lib/test_merchant_api.c | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/lib/test_merchant_api_twisted.c | 579++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/lib/testing_api_cmd_check_payment.c | 1+
Msrc/lib/testing_api_cmd_history.c | 24++++++++++++------------
Msrc/lib/testing_api_cmd_pay_abort_refund.c | 4+++-
Msrc/lib/testing_api_cmd_poll_payment.c | 1+
Msrc/lib/testing_api_cmd_proposal.c | 3++-
Msrc/lib/testing_api_cmd_refund_increase.c | 7+++++++
Msrc/lib/testing_api_cmd_refund_lookup.c | 302+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/lib/testing_api_cmd_track_transaction.c | 15+++++++++------
29 files changed, 1424 insertions(+), 711 deletions(-)

diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -406,6 +406,7 @@ do_shutdown (void *cls) (void) cls; MH_force_pc_resume (); MH_force_trh_resume (); + MH_force_refund_resume (); if (NULL != mhd_task) { GNUNET_SCHEDULER_cancel (mhd_task); diff --git a/src/backend/taler-merchant-httpd_check-payment.c b/src/backend/taler-merchant-httpd_check-payment.c @@ -140,14 +140,16 @@ cprc_cleanup (struct TM_HandlerContext *hc) * * @param cls closure * @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 coin_pub + * @param refund_amount refund amount which is being taken from @a coin_pub * @param refund_fee cost of this refund operation */ static void process_refunds_cb (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, diff --git a/src/backend/taler-merchant-httpd_order.c b/src/backend/taler-merchant-httpd_order.c @@ -488,12 +488,14 @@ proposal_put (struct MHD_Connection *connection, refund_deadline.abs_value_us) { GNUNET_JSON_parse_free (spec); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invariant failed: wire_transfer_deadline >= refund_deadline\n"); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "wire_transfer_deadline: %s\n", + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "wire_transfer_deadline: %s\n", GNUNET_STRINGS_absolute_time_to_string ( wire_transfer_deadline)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "refund_deadline: %s\n", + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "refund_deadline: %s\n", GNUNET_STRINGS_absolute_time_to_string (refund_deadline)); return TALER_MHD_reply_with_error (connection, diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c @@ -934,7 +934,7 @@ check_payment_sufficient (struct PayContext *pc) { GNUNET_break_op (0); resume_pay_with_error (pc, - MHD_HTTP_BAD_REQUEST, + 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)"); } @@ -942,7 +942,7 @@ check_payment_sufficient (struct PayContext *pc) { GNUNET_break_op (0); resume_pay_with_error (pc, - MHD_HTTP_BAD_REQUEST, + MHD_HTTP_NOT_ACCEPTABLE, TALER_EC_PAY_PAYMENT_INSUFFICIENT, "payment insufficient"); @@ -1021,9 +1021,9 @@ deposit_cb (void *cls, "exchange had an internal server error", "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED, - "exchange-code", + "exchange_code", (json_int_t) hr->ec, - "exchange-http-status", + "exchange_http_status", (json_int_t) hr->http_status)); } else if (NULL == hr->reply) @@ -1037,9 +1037,9 @@ deposit_cb (void *cls, "exchange failed, response body not even in JSON", "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED, - "exchange-code", + "exchange_code", (json_int_t) hr->ec, - "exchange-http-status", + "exchange_http_status", (json_int_t) hr->http_status)); } else @@ -1055,13 +1055,13 @@ deposit_cb (void *cls, "exchange failed on deposit of a coin", "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED, - "exchange-code", + "exchange_code", (json_int_t) hr->ec, - "exchange-http-status", + "exchange_http_status", (json_int_t) hr->http_status, "coin_pub", GNUNET_JSON_from_data_auto (&dc->coin_pub), - "exchange-reply", + "exchange_reply", hr->reply)); else resume_pay_with_response ( @@ -1072,13 +1072,13 @@ deposit_cb (void *cls, "exchange failed on deposit of a coin", "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED, - "exchange-code", + "exchange_code", (json_int_t) hr->ec, - "exchange-http-status", + "exchange_http_status", (json_int_t) hr->http_status, "coin_pub", GNUNET_JSON_from_data_auto (&dc->coin_pub), - "exchange-reply", + "exchange_reply", hr->reply)); } return; @@ -1172,11 +1172,11 @@ process_pay_with_exchange (void *cls, "failed to obtain meta-data from exchange", "code", (json_int_t) TALER_EC_PAY_EXCHANGE_KEYS_FAILURE, - "exchange-http-status", + "exchange_http_status", (json_int_t) http_status, - "exchange-code", + "exchange_code", (json_int_t) ec, - "exchange-reply", + "exchange_reply", error_reply)); return; } @@ -1789,14 +1789,16 @@ parse_pay (struct MHD_Connection *connection, * * @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 coin_pub + * @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, @@ -1804,6 +1806,7 @@ check_coin_refunded (void *cls, { struct PayContext *pc = cls; + (void) exchange_url; for (unsigned int i = 0; i<pc->coins_cnt; i++) { struct DepositConfirmation *dc = &pc->dc[i]; diff --git a/src/backend/taler-merchant-httpd_poll-payment.c b/src/backend/taler-merchant-httpd_poll-payment.c @@ -140,14 +140,16 @@ pprc_cleanup (struct TM_HandlerContext *hc) * * @param cls closure * @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 coin_pub + * @param refund_amount refund amount which is being taken from @a coin_pub * @param refund_fee cost of this refund operation */ static void process_refunds_cb (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, diff --git a/src/backend/taler-merchant-httpd_refund.c b/src/backend/taler-merchant-httpd_refund.c @@ -66,14 +66,16 @@ struct ProcessRefundData * * @param cls closure * @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 coin_pub + * @param refund_amount refund amount which is being taken from @a coin_pub * @param refund_fee cost of this refund operation */ static void process_refunds_cb (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, @@ -83,6 +85,7 @@ process_refunds_cb (void *cls, struct GNUNET_CRYPTO_EddsaSignature sig; json_t *element; + (void) exchange_url; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found refund of %s for coin %s with reason `%s' in database\n", TALER_B2S (coin_pub), diff --git a/src/backend/taler-merchant-httpd_refund.h b/src/backend/taler-merchant-httpd_refund.h @@ -45,6 +45,14 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh, size_t *upload_data_size, struct MerchantInstance *mi); + +/** + * Force resuming all suspended refund lookups, needed during shutdown. + */ +void +MH_force_refund_resume (void); + + /** * Get the JSON representation of a refund. * diff --git a/src/backend/taler-merchant-httpd_refund_lookup.c b/src/backend/taler-merchant-httpd_refund_lookup.c @@ -22,9 +22,402 @@ #include <jansson.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_exchanges.h" #include "taler-merchant-httpd_refund.h" +/** + * How often do we retry DB transactions on serialization failures? + */ +#define MAX_RETRIES 5 + +/** + * Information we keep for each coin to be refunded. + */ +struct CoinRefund +{ + + /** + * Kept in a DLL. + */ + struct CoinRefund *next; + + /** + * Kept in a DLL. + */ + struct CoinRefund *prev; + + /** + * Request to connect to the target exchange. + */ + struct TMH_EXCHANGES_FindOperation *fo; + + /** + * Handle for the refund operation with the exchange. + */ + struct TALER_EXCHANGE_RefundHandle *rh; + + /** + * PRD this operation is part of. + */ + struct ProcessRefundData *prd; + + /** + * Coin to refund. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Refund transaction ID to use. + */ + uint64_t rtransaction_id; + + /** + * Amount to refund. + */ + struct TALER_Amount refund_amount; + + /** + * Applicable refund transaction fee. + */ + struct TALER_Amount refund_fee; + + /** + * Public key of the exchange affirming the refund. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * Signature of the exchange affirming the refund. + */ + struct TALER_ExchangeSignatureP exchange_sig; + + /** + * HTTP status from the exchange, #MHD_HTTP_OK if + * @a exchange_pub and @a exchange_sig are valid. + */ + unsigned int exchange_status; + + /** + * HTTP error code from the exchange. + */ + enum TALER_ErrorCode exchange_code; + + /** + * Fully reply from the exchange, only possibly set if + * we got a JSON reply and a non-#MHD_HTTP_OK error code + */ + json_t *exchange_reply; + +}; + + +/** + * Closure for #process_refunds_cb. + */ +struct ProcessRefundData +{ + /** + * Must be first for #handle_mhd_completion_callback() cleanup + * logic to work. + */ + struct TM_HandlerContext hc; + + /** + * Hashed version of contract terms. + */ + struct GNUNET_HashCode h_contract_terms; + + /** + * DLL of (suspended) requests. + */ + struct ProcessRefundData *next; + + /** + * DLL of (suspended) requests. + */ + struct ProcessRefundData *prev; + + /** + * Head of DLL of coin refunds for this request. + */ + struct CoinRefund *cr_head; + + /** + * Tail of DLL of coin refunds for this request. + */ + struct CoinRefund *cr_tail; + + /** + * Both public and private key are needed by the callback + */ + const struct MerchantInstance *merchant; + + /** + * Connection we are handling. + */ + struct MHD_Connection *connection; + + /** + * Did we suspend @a connection? + */ + int suspended; + + /** + * Return code: #TALER_EC_NONE if successful. + */ + enum TALER_ErrorCode ec; +}; + + +/** + * HEad of DLL of (suspended) requests. + */ +static struct ProcessRefundData *prd_head; + +/** + * Tail of DLL of (suspended) requests. + */ +static struct ProcessRefundData *prd_tail; + + +/** + * Clean up memory in @a cls, the connection was closed. + * + * @param cls a `struct ProcessRefundData` to clean up. + */ +static void +cleanup_prd (struct TM_HandlerContext *cls) +{ + struct ProcessRefundData *prd = (struct ProcessRefundData *) cls; + struct CoinRefund *cr; + + while (NULL != (cr = prd->cr_head)) + { + GNUNET_CONTAINER_DLL_remove (prd->cr_head, + prd->cr_tail, + cr); + if (NULL != cr->fo) + { + TMH_EXCHANGES_find_exchange_cancel (cr->fo); + cr->fo = NULL; + } + if (NULL != cr->rh) + { + TALER_EXCHANGE_refund_cancel (cr->rh); + cr->rh = NULL; + } + if (NULL != cr->exchange_reply) + { + json_decref (cr->exchange_reply); + cr->exchange_reply = NULL; + } + GNUNET_free (cr); + } + GNUNET_free (prd); +} + + +/** + * Check if @a prd has sub-activities still pending. + * + * @param prd request to check + * @return #GNUNET_YES if activities are still pending + */ +static int +prd_pending (struct ProcessRefundData *prd) +{ + int pending = GNUNET_NO; + + for (struct CoinRefund *cr = prd->cr_head; + NULL != cr; + cr = cr->next) + { + if ( (NULL != cr->fo) || + (NULL != cr->rh) ) + { + pending = GNUNET_YES; + break; + } + } + return pending; +} + + +/** + * Check if @a prd is ready to be resumed, and if so, do it. + * + * @param prd refund request to be possibly ready + */ +static void +check_resume_prd (struct ProcessRefundData *prd) +{ + if (prd_pending (prd)) + return; + GNUNET_CONTAINER_DLL_remove (prd_head, + prd_tail, + prd); + GNUNET_assert (prd->suspended); + prd->suspended = GNUNET_NO; + MHD_resume_connection (prd->connection); + TMH_trigger_daemon (); +} + + +/** + * Callbacks of this type are used to serve the result of submitting a + * refund request to an exchange. + * + * @param cls a `struct CoinRefund` + * @param hr HTTP response data + * @param exchange_pub exchange key used to sign refund confirmation + * @param exchange_sig exchange's signature over refund + */ +static void +refund_cb (void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct CoinRefund *cr = cls; + + cr->rh = NULL; + cr->exchange_status = hr->http_status; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Exchange refund status for coin %s is %u\n", + TALER_B2S (&cr->coin_pub), + hr->http_status); + if (MHD_HTTP_OK != hr->http_status) + { + cr->exchange_code = hr->ec; + cr->exchange_reply = json_incref ((json_t*) hr->reply); + } + else + { + cr->exchange_pub = *exchange_pub; + cr->exchange_sig = *exchange_sig; + /* FIXME: store in our database, + 1) as evidence for us that the refund happened, and + 2) to possibly avoid doing another exchange iteration + the next time around. */ + } + check_resume_prd (cr->prd); +} + + +/** + * Function called with the result of a #TMH_EXCHANGES_find_exchange() + * operation. + * + * @param cls a `struct CoinRefund *` + * @param eh handle to the exchange context + * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available + * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config + * @param ec error code, #TALER_EC_NONE on success + * @param http_status the HTTP status we got from the exchange + * @param error_reply the full reply from the exchange, NULL if + * the response was NOT in JSON or on success + */ +static void +exchange_found_cb (void *cls, + struct TALER_EXCHANGE_Handle *eh, + const struct TALER_Amount *wire_fee, + int exchange_trusted, + enum TALER_ErrorCode ec, + unsigned int http_status, + const json_t *error_reply) +{ + struct CoinRefund *cr = cls; + + cr->fo = NULL; + if (TALER_EC_NONE == ec) + { + cr->rh = TALER_EXCHANGE_refund (eh, + &cr->refund_amount, + &cr->refund_fee, + &cr->prd->h_contract_terms, + &cr->coin_pub, + cr->rtransaction_id, + &cr->prd->merchant->privkey, + &refund_cb, + cr); + return; + } + cr->exchange_status = http_status; + cr->exchange_code = ec; + cr->exchange_reply = json_incref ((json_t*) error_reply); + check_resume_prd (cr->prd); +} + + +/** + * Function called with information about a refund. + * It is responsible for packing up the data to return. + * + * @param cls closure + * @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 +process_refunds_cb (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 ProcessRefundData *prd = cls; + struct CoinRefund *cr; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Found refund of %s for coin %s with reason `%s' in database\n", + TALER_B2S (coin_pub), + TALER_amount2s (refund_amount), + reason); + cr = GNUNET_new (struct CoinRefund); + cr->prd = prd; + cr->coin_pub = *coin_pub; + cr->rtransaction_id = rtransaction_id; + cr->refund_amount = *refund_amount; + cr->refund_fee = *refund_fee; + GNUNET_CONTAINER_DLL_insert (prd->cr_head, + prd->cr_tail, + cr); + /* FIXME: check in database if we already got the + results from #refund_cb() from an earlier request, + if so, avoid this step: */ + cr->fo = TMH_EXCHANGES_find_exchange (exchange_url, + NULL, + &exchange_found_cb, + cr); +} + + +/** + * Force resuming all suspended refund lookups, needed during shutdown. + */ +void +MH_force_refund_resume (void) +{ + struct ProcessRefundData *prd; + + while (NULL != (prd = prd_head)) + { + GNUNET_CONTAINER_DLL_remove (prd_head, + prd_tail, + prd); + GNUNET_assert (prd->suspended); + prd->suspended = GNUNET_NO; + MHD_resume_connection (prd->connection); + } +} + /** * Return refund situation about a contract. @@ -45,91 +438,181 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh, size_t *upload_data_size, struct MerchantInstance *mi) { + struct ProcessRefundData *prd; const char *order_id; - struct GNUNET_HashCode h_contract_terms; json_t *contract_terms; enum GNUNET_DB_QueryStatus qs; - order_id = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "order_id"); - if (NULL == order_id) + prd = *connection_cls; + if (NULL == prd) { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_PARAMETER_MISSING, - "order_id"); + order_id = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "order_id"); + if (NULL == order_id) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_PARAMETER_MISSING, + "order_id"); + } + + /* Convert order id to h_contract_terms */ + contract_terms = NULL; + db->preflight (db->cls); + qs = db->find_contract_terms (db->cls, + &contract_terms, + order_id, + &mi->pubkey); + if (0 > qs) + { + /* 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); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFUND_LOOKUP_DB_ERROR, + "database error looking up order_id from merchant_contract_terms table"); + } + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Unknown order id given: `%s'\n", + order_id); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFUND_ORDER_ID_UNKNOWN, + "order_id not found in database"); + } + + prd = GNUNET_new (struct ProcessRefundData); + if (GNUNET_OK != + TALER_JSON_hash (contract_terms, + &prd->h_contract_terms)) + { + GNUNET_break (0); + json_decref (contract_terms); + GNUNET_free (prd); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_LOGIC_ERROR, + "Could not hash contract terms"); + } + json_decref (contract_terms); + prd->hc.cc = &cleanup_prd; + prd->merchant = mi; + prd->ec = TALER_EC_NONE; + prd->connection = connection; + *connection_cls = prd; + + for (unsigned int i = 0; i<MAX_RETRIES; i++) + { + qs = db->get_refunds_from_contract_terms_hash (db->cls, + &mi->pubkey, + &prd->h_contract_terms, + &process_refunds_cb, + prd); + if (GNUNET_DB_STATUS_SOFT_ERROR != qs) + break; + } + if (0 > qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database hard error on refunds_from_contract_terms_hash lookup: %s\n", + GNUNET_h2s (&prd->h_contract_terms)); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFUND_LOOKUP_DB_ERROR, + "Failed to lookup refunds for contract"); + } } - /* Convert order id to h_contract_terms */ - contract_terms = NULL; - db->preflight (db->cls); - qs = db->find_contract_terms (db->cls, - &contract_terms, - order_id, - &mi->pubkey); - if (0 > qs) + /* Check if there are still exchange operations pending */ + if (GNUNET_YES == prd_pending (prd)) { - /* 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); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_REFUND_LOOKUP_DB_ERROR, - "database error looking up order_id from merchant_contract_terms table"); + if (! prd->suspended) + { + prd->suspended = GNUNET_YES; + MHD_suspend_connection (connection); + GNUNET_CONTAINER_DLL_insert (prd_head, + prd_tail, + prd); + } + return MHD_YES; /* we're still talking to the exchange */ } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + /* All operations done, build final response */ + if (NULL == prd->cr_head) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Unknown order id given: `%s'\n", - order_id); + /* There ARE no refunds scheduled, bitch */ return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, - TALER_EC_REFUND_ORDER_ID_UNKNOWN, - "order_id not found in database"); + TALER_EC_REFUND_LOOKUP_NO_REFUND, + "This contract is not currently eligible for refunds"); } - if (GNUNET_OK != - TALER_JSON_hash (contract_terms, - &h_contract_terms)) { - GNUNET_break (0); - json_decref (contract_terms); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_INTERNAL_LOGIC_ERROR, - "Could not hash contract terms"); - } - json_decref (contract_terms); + json_t *ra; - { - json_t *response; - enum TALER_ErrorCode ec; - const char *errmsg; - - response = TM_get_refund_json (mi, - &h_contract_terms, - &ec, - &errmsg); - if (NULL == response) - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - ec, - errmsg); - return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK, - "{s:o, s:o, s:o}", - "refund_permissions", - response, - "merchant_pub", - GNUNET_JSON_from_data_auto ( - &mi->pubkey), - "h_contract_terms", - GNUNET_JSON_from_data_auto ( - &h_contract_terms)); + ra = json_array (); + GNUNET_assert (NULL != ra); + for (struct CoinRefund *cr = prd->cr_head; + NULL != cr; + cr = cr->next) + { + GNUNET_assert ( + 0 == + json_array_append_new ( + ra, + (MHD_HTTP_OK != cr->exchange_status) + ? json_pack ((NULL != cr->exchange_reply) + ? "{s:o,s:o,s:o,s:I,s:I,s:I,s:O}" + : "{s:o,s:o,s:o,s:I,s:I:s:I}", + "coin_pub", + GNUNET_JSON_from_data_auto (&cr->coin_pub), + "refund_amount", + TALER_JSON_from_amount (&cr->refund_amount), + "refund_fee", + TALER_JSON_from_amount (&cr->refund_fee), + "exchange_http_status", + (json_int_t) cr->exchange_status, + "rtransaction_id", + (json_int_t) cr->rtransaction_id, + "exchange_code", + (json_int_t) cr->exchange_code, + "exchange_reply", + cr->exchange_reply) + : json_pack ("{s:o,s:o,s:o,s:I,s:I,s:o,s:o}", + "coin_pub", + GNUNET_JSON_from_data_auto (&cr->coin_pub), + "refund_amount", + TALER_JSON_from_amount (&cr->refund_amount), + "refund_fee", + TALER_JSON_from_amount (&cr->refund_fee), + "exchange_http_status", + (json_int_t) cr->exchange_status, + "rtransaction_id", + (json_int_t) cr->rtransaction_id, + "exchange_pub", + GNUNET_JSON_from_data_auto (&cr->exchange_pub), + "exchange_sig", + GNUNET_JSON_from_data_auto (&cr->exchange_sig) + ))); + } + return TALER_MHD_reply_json_pack ( + connection, + MHD_HTTP_OK, + "{s:o, s:o, s:o}", + "refunds", + ra, + "merchant_pub", + GNUNET_JSON_from_data_auto (&mi->pubkey), + "h_contract_terms", + GNUNET_JSON_from_data_auto (&prd->h_contract_terms)); } } diff --git a/src/backend/taler-merchant-httpd_tip-reserve-helper.c b/src/backend/taler-merchant-httpd_tip-reserve-helper.c @@ -101,10 +101,10 @@ handle_status (void *cls, TALER_MHD_make_json_pack ( "{s:I, s:I, s:s, s:I, s:O}", "code", (json_int_t) TALER_EC_TIP_QUERY_RESERVE_UNKNOWN_TO_EXCHANGE, - "exchange-http-status", hr->http_status, + "exchange_http_status", hr->http_status, "hint", "tipping reserve unknown at exchange", - "exchange-code", hr->ec, - "exchange-reply", hr->reply)); + "exchange_code", hr->ec, + "exchange_reply", hr->reply)); return; } if (MHD_HTTP_OK != hr->http_status) @@ -116,10 +116,10 @@ handle_status (void *cls, TALER_MHD_make_json_pack ( "{s:I, s:I, s:s, s:I, s:O}", "code", (json_int_t) TALER_EC_TIP_QUERY_RESERVE_HISTORY_FAILED, - "exchange-http-status", hr->http_status, + "exchange_http_status", hr->http_status, "hint", "exchange failed to provide reserve history", - "exchange-code", (json_int_t) hr->ec, - "exchange-reply", hr->reply)); + "exchange_code", (json_int_t) hr->ec, + "exchange_reply", hr->reply)); return; } diff --git a/src/backend/taler-merchant-httpd_track-transaction.c b/src/backend/taler-merchant-httpd_track-transaction.c @@ -471,11 +471,11 @@ wire_deposits_cb (void *cls, "{s:I, s:I, s:I, s:O}", "code", (json_int_t) TALER_EC_TRACK_TRANSACTION_WIRE_TRANSFER_TRACE_ERROR, - "exchange-http-status", + "exchange_http_status", (json_int_t) hr->http_status, - "exchange-code", + "exchange_code", (json_int_t) hr->ec, - "exchange-reply", + "exchange_reply", hr->reply)); return; } @@ -629,11 +629,11 @@ wtid_cb (void *cls, "{s:I, s:I, s:I, s:O}", "code", (json_int_t) TALER_EC_TRACK_TRANSACTION_COIN_TRACE_ERROR, - "exchange-http-status", + "exchange_http_status", (json_int_t) hr->http_status, - "exchange-code", + "exchange_code", (json_int_t) hr->ec, - "exchange-reply", + "exchange_reply", hr->reply)); return; } @@ -905,9 +905,9 @@ process_track_transaction_with_exchange (void *cls, : "{s:s, s:I, s:I, s:I}", "hint", "failed to obtain meta-data from exchange", "code", (json_int_t) TALER_EC_TRACK_TRANSACTION_EXCHANGE_KEYS_FAILURE, - "exchange-http-status", (json_int_t) http_status, - "exchange-code", (json_int_t) ec, - "exchange-reply", error_reply)); + "exchange_http_status", (json_int_t) http_status, + "exchange_code", (json_int_t) ec, + "exchange_reply", error_reply)); return; } tctx->eh = eh; diff --git a/src/backend/taler-merchant-httpd_track-transfer.c b/src/backend/taler-merchant-httpd_track-transfer.c @@ -617,9 +617,9 @@ wire_transfer_cb (void *cls, TALER_MHD_make_json_pack ( "{s:I, s:I, s:I, s:O}", "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_ERROR, - "exchange-code", (json_int_t) hr->ec, - "exchange-http-status", (json_int_t) hr->http_status, - "exchange-reply", hr->reply)); + "exchange_code", (json_int_t) hr->ec, + "exchange_http_status", (json_int_t) hr->http_status, + "exchange_reply", hr->reply)); return; } for (unsigned int i = 0; i<MAX_RETRIES; i++) @@ -828,9 +828,9 @@ process_track_transfer_with_exchange (void *cls, : "{s:s, s:I, s:I, s:I}", "hint", "failed to obtain meta-data from exchange", "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_KEYS_FAILURE, - "exchange-http-status", (json_int_t) http_status, - "exchange-code", (json_int_t) ec, - "exchange-reply", error_reply)); + "exchange_http_status", (json_int_t) http_status, + "exchange_code", (json_int_t) ec, + "exchange_reply", error_reply)); return; } rctx->eh = eh; diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql @@ -21,7 +21,7 @@ BEGIN; SELECT _v.register_patch('merchant-0001', NULL, NULL); -CREATE TABLE IF NOT EXISTS merchant_orders +CREATE TABLE IF NOT EXISTS merchant_orders (order_id VARCHAR NOT NULL ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) ,contract_terms BYTEA NOT NULL @@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS merchant_orders ,PRIMARY KEY (order_id, merchant_pub) ); --- Offers we made to customers +-- Offers we made to customers CREATE TABLE IF NOT EXISTS merchant_contract_terms (order_id VARCHAR NOT NULL ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) @@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS merchant_contract_terms ,UNIQUE (h_contract_terms, merchant_pub) ); --- Table with the proofs for each coin we deposited at the exchange +-- Table with the proofs for each coin we deposited at the exchange CREATE TABLE IF NOT EXISTS merchant_deposits (h_contract_terms BYTEA NOT NULL ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) @@ -62,7 +62,7 @@ CREATE TABLE IF NOT EXISTS merchant_deposits ,FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub) ); -CREATE TABLE IF NOT EXISTS merchant_proofs +CREATE TABLE IF NOT EXISTS merchant_proofs (exchange_url VARCHAR NOT NULL ,wtid BYTEA CHECK (LENGTH(wtid)=32) ,execution_time INT8 NOT NULL @@ -70,10 +70,10 @@ CREATE TABLE IF NOT EXISTS merchant_proofs ,proof BYTEA NOT NULL ,PRIMARY KEY (wtid, exchange_url) ); - + -- Note that h_contract_terms + coin_pub may actually be unknown to -- us, e.g. someone else deposits something for us at the exchange. --- Hence those cannot be foreign keys into deposits/transactions! +-- Hence those cannot be foreign keys into deposits/transactions! CREATE TABLE IF NOT EXISTS merchant_transfers (h_contract_terms BYTEA NOT NULL ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) @@ -88,7 +88,7 @@ CREATE INDEX IF NOT EXISTS merchant_transfers_by_wtid ON merchant_transfers (wtid); -CREATE TABLE IF NOT EXISTS exchange_wire_fees +CREATE TABLE IF NOT EXISTS exchange_wire_fees (exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32) ,h_wire_method BYTEA NOT NULL CHECK (LENGTH(h_wire_method)=64) ,wire_fee_val INT8 NOT NULL @@ -100,20 +100,20 @@ CREATE TABLE IF NOT EXISTS exchange_wire_fees ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64) ,PRIMARY KEY (exchange_pub,h_wire_method,start_date,end_date) ); - + CREATE TABLE IF NOT EXISTS merchant_refunds (rtransaction_id BIGSERIAL UNIQUE - ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,merchant_pub BYTEA NOT NULL ,h_contract_terms BYTEA NOT NULL - ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) + ,coin_pub BYTEA NOT NULL ,reason VARCHAR NOT NULL ,refund_amount_val INT8 NOT NULL ,refund_amount_frac INT4 NOT NULL - ,refund_fee_val INT8 NOT NULL - ,refund_fee_frac INT4 NOT NULL + ,FOREIGN KEY (h_contract_terms, coin_pub) REFERENCES merchant_deposits (h_contract_terms, coin_pub) + ,FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub) ); --- balances of the reserves available for tips +-- balances of the reserves available for tips CREATE TABLE IF NOT EXISTS merchant_tip_reserves (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32) ,expiration INT8 NOT NULL @@ -121,8 +121,8 @@ CREATE TABLE IF NOT EXISTS merchant_tip_reserves ,balance_frac INT4 NOT NULL ,PRIMARY KEY (reserve_priv) ); - --- table where we remember when tipping reserves where established / enabled + +-- table where we remember when tipping reserves where established / enabled CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32) ,credit_uuid BYTEA UNIQUE NOT NULL CHECK (LENGTH(credit_uuid)=64) @@ -132,8 +132,8 @@ CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits ,PRIMARY KEY (credit_uuid) ); --- tips that have been authorized -CREATE TABLE IF NOT EXISTS merchant_tips +-- tips that have been authorized +CREATE TABLE IF NOT EXISTS merchant_tips (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32) ,tip_id BYTEA NOT NULL CHECK (LENGTH(tip_id)=64) ,exchange_url VARCHAR NOT NULL @@ -148,7 +148,7 @@ CREATE TABLE IF NOT EXISTS merchant_tips ); -- tips that have been picked up -CREATE TABLE IF NOT EXISTS merchant_tip_pickups +CREATE TABLE IF NOT EXISTS merchant_tip_pickups (tip_id BYTEA NOT NULL REFERENCES merchant_tips (tip_id) ON DELETE CASCADE ,pickup_id BYTEA NOT NULL CHECK (LENGTH(pickup_id)=64) ,amount_val INT8 NOT NULL @@ -156,7 +156,7 @@ CREATE TABLE IF NOT EXISTS merchant_tip_pickups ,PRIMARY KEY (pickup_id) ); --- sessions and their order_id/fulfillment_url mapping +-- sessions and their order_id/fulfillment_url mapping CREATE TABLE IF NOT EXISTS merchant_session_info (session_id VARCHAR NOT NULL ,fulfillment_url VARCHAR NOT NULL diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -1742,9 +1742,12 @@ get_refunds_cb (void *cls, struct TALER_Amount refund_amount; struct TALER_Amount refund_fee; char *reason; + char *exchange_url; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub), + GNUNET_PQ_result_spec_string ("exchange_url", + &exchange_url), GNUNET_PQ_result_spec_uint64 ("rtransaction_id", &rtransaction_id), TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", @@ -1768,6 +1771,7 @@ get_refunds_cb (void *cls, grc->qs = i + 1; grc->rc (grc->rc_cls, &coin_pub, + exchange_url, rtransaction_id, reason, &refund_amount, @@ -1788,15 +1792,12 @@ get_refunds_cb (void *cls, * @return transaction status */ static enum GNUNET_DB_QueryStatus -postgres_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) +postgres_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) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -1837,7 +1838,6 @@ postgres_get_refunds_from_contract_terms_hash (void *cls, * @param coin_pub public key of the coin giving the (part of) refund * @param reason human readable explanation behind the refund * @param refund how much this coin is refunding - * @param refund_fee refund fee for this coin */ static enum GNUNET_DB_QueryStatus insert_refund (void *cls, @@ -1845,8 +1845,7 @@ insert_refund (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_CoinSpendPublicKeyP *coin_pub, const char *reason, - const struct TALER_Amount *refund, - const struct TALER_Amount *refund_fee) + const struct TALER_Amount *refund) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -1855,7 +1854,6 @@ insert_refund (void *cls, GNUNET_PQ_query_param_auto_from_type (coin_pub), GNUNET_PQ_query_param_string (reason), TALER_PQ_query_param_amount (refund), - TALER_PQ_query_param_amount (refund_fee), GNUNET_PQ_query_param_end }; @@ -1886,17 +1884,18 @@ insert_refund (void *cls, * @return transaction status code */ static enum GNUNET_DB_QueryStatus -postgres_store_wire_fee_by_exchange (void *cls, - const struct - TALER_MasterPublicKeyP *exchange_pub, - const struct - GNUNET_HashCode *h_wire_method, - const struct TALER_Amount *wire_fee, - const struct TALER_Amount *closing_fee, - struct GNUNET_TIME_Absolute start_date, - struct GNUNET_TIME_Absolute end_date, - const struct - TALER_MasterSignatureP *exchange_sig) +postgres_store_wire_fee_by_exchange ( + void *cls, + const struct + TALER_MasterPublicKeyP *exchange_pub, + const struct + GNUNET_HashCode *h_wire_method, + const struct TALER_Amount *wire_fee, + const struct TALER_Amount *closing_fee, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute end_date, + const struct + TALER_MasterSignatureP *exchange_sig) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -2109,7 +2108,6 @@ process_deposits_for_refund_cb (void *cls, struct TALER_Amount deposit_refund[GNUNET_NZL (num_results)]; struct TALER_CoinSpendPublicKeyP deposit_coin_pubs[GNUNET_NZL (num_results)]; struct TALER_Amount deposit_amount_with_fee[GNUNET_NZL (num_results)]; - struct TALER_Amount deposit_refund_fee[GNUNET_NZL (num_results)]; GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (ctx->refund->currency, @@ -2121,14 +2119,11 @@ process_deposits_for_refund_cb (void *cls, { struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_Amount amount_with_fee; - struct TALER_Amount refund_fee; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &amount_with_fee), - TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", - &refund_fee), GNUNET_PQ_result_spec_end }; struct FindRefundContext ictx = { @@ -2173,7 +2168,6 @@ process_deposits_for_refund_cb (void *cls, deposit_refund[i] = ictx.refunded_amount; deposit_amount_with_fee[i] = amount_with_fee; deposit_coin_pubs[i] = coin_pub; - deposit_refund_fee[i] = refund_fee; if (0 > TALER_amount_add (&current_refund, &current_refund, @@ -2283,8 +2277,7 @@ process_deposits_for_refund_cb (void *cls, ctx->h_contract_terms, &deposit_coin_pubs[i], ctx->reason, - increment, - &deposit_refund_fee[i]))) + increment))) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); ctx->qs = qs; @@ -3074,11 +3067,9 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) ",reason" ",refund_amount_val" ",refund_amount_frac" - ",refund_fee_val" - ",refund_fee_frac" ") VALUES" - "($1, $2, $3, $4, $5, $6, $7, $8)", - 8), + "($1, $2, $3, $4, $5, $6)", + 6), GNUNET_PQ_make_prepare ("insert_proof", "INSERT INTO merchant_proofs" "(exchange_url" @@ -3228,15 +3219,17 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash", "SELECT" " coin_pub" + ",merchant_deposits.exchange_url" ",rtransaction_id" ",refund_amount_val" ",refund_amount_frac" - ",refund_fee_val" - ",refund_fee_frac" + ",merchant_deposits.refund_fee_val" + ",merchant_deposits.refund_fee_frac" ",reason" " FROM merchant_refunds" - " WHERE merchant_pub=$1" - " AND h_contract_terms=$2", + " JOIN merchant_deposits USING (merchant_pub, coin_pub)" + " WHERE merchant_refunds.merchant_pub=$1" + " AND merchant_refunds.h_contract_terms=$2", 2), GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_asc", "SELECT" @@ -3354,9 +3347,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) ",merchant_deposits.exchange_proof" " FROM merchant_transfers" " JOIN merchant_deposits" - " ON (merchant_deposits.h_contract_terms = merchant_transfers.h_contract_terms" - " AND" - " merchant_deposits.coin_pub = merchant_transfers.coin_pub)" + " USING (h_contract_terms,coin_pub)" " WHERE wtid=$1", 1), GNUNET_PQ_make_prepare ("find_proof_by_wtid", diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -199,14 +199,16 @@ static json_t *contract_terms_future; * * @param cls closure * @param coin_pub public coin from which the refund comes from + * @param exchange_url URL of the exchange that issued the @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 coin_pub + * @param refund_amount refund amount which is being taken from @a coin_pub * @param refund_fee cost of this refund operation */ static void refund_cb (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, diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -299,15 +299,70 @@ struct TALER_MERCHANT_RefundLookupOperation; /** + * Detail about a refund lookup result. + */ +struct TALER_MERCHANT_RefundDetail +{ + + /** + * Exchange response details. Full details are only included + * upon failure (HTTP status is not #MHD_HTTP_OK). + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Coin this detail is about. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Refund transaction ID used. + */ + uint64_t rtransaction_id; + + /** + * Amount to be refunded for this coin. + */ + struct TALER_Amount refund_amount; + + /** + * Applicable refund transaction fee. + */ + struct TALER_Amount refund_fee; + + /** + * Public key of the exchange affirming the refund, + * only valid if the @e hr http_status is #MHD_HTTP_OK. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * Signature of the exchange affirming the refund, + * only valid if the @e hr http_status is #MHD_HTTP_OK. + */ + struct TALER_ExchangeSignatureP exchange_sig; + +}; + + +/** * Callback to process a GET /refund request * * @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 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); /** diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -138,15 +138,17 @@ typedef void * * @param cls closure * @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 coin_pub + * @param refund_amount refund amount which is being taken from @a coin_pub * @param refund_fee cost of this refund operation */ typedef void (*TALER_MERCHANTDB_RefundCallback)( 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, diff --git a/src/lib/merchant_api_common.c b/src/lib/merchant_api_common.c @@ -57,13 +57,13 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response, hr->ec = TALER_JSON_get_error_code (response); hr->hint = TALER_JSON_get_error_hint (response); - /* handle 'exchange-http-status' */ + /* handle 'exchange_http_status' */ jc = json_object_get (response, - "exchange-http-status"); + "exchange_http_status"); /* The caller already knows that the JSON represents an error, so we are dealing with a missing error code here. */ if (NULL == jc) - return; /* no need to bother with exchange-code/hint if we had no status */ + return; /* no need to bother with exchange_code/hint if we had no status */ if (! json_is_integer (jc)) { GNUNET_break_op (0); @@ -71,9 +71,9 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response, } hr->exchange_http_status = (unsigned int) json_integer_value (jc); - /* handle 'exchange-reply' */ + /* handle 'exchange_reply' */ jc = json_object_get (response, - "exchange-reply"); + "exchange_reply"); if (! json_is_object (jc)) { GNUNET_break_op (0); @@ -83,9 +83,9 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response, hr->exchange_reply = jc; } - /* handle 'exchange-code' */ + /* handle 'exchange_code' */ jc = json_object_get (response, - "exchange-code"); + "exchange_code"); /* The caller already knows that the JSON represents an error, so we are dealing with a missing error code here. */ if (NULL == jc) diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c @@ -298,7 +298,7 @@ check_conflict (struct TALER_MERCHANT_Pay *ph, json_t *ereply; struct TALER_CoinSpendPublicKeyP coin_pub; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("exchange-reply", &ereply), + GNUNET_JSON_spec_json ("exchange_reply", &ereply), GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), GNUNET_JSON_spec_end () }; diff --git a/src/lib/merchant_api_refund.c b/src/lib/merchant_api_refund.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015, 2016, 2017, 2019 Taler Systems SA + Copyright (C) 2014-2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -32,6 +32,9 @@ #include <taler/taler_curl_lib.h> +/** + * Handle to the refund lookup operation. + */ struct TALER_MERCHANT_RefundLookupOperation { /** @@ -82,6 +85,180 @@ TALER_MERCHANT_refund_lookup_cancel ( /** + * Check that the @a reply to the @a rlo is valid + * + * @param rlo lookup operation + * @param reply JSON reply to verify + * @return #TALER_EC_NONE if @a reply is well-formed + */ +static enum TALER_ErrorCode +check_refund_result (struct TALER_MERCHANT_RefundLookupOperation *rlo, + const json_t *reply) +{ + json_t *refunds; + unsigned int num_refunds; + struct GNUNET_HashCode h_contract_terms; + struct TALER_MerchantPublicKeyP merchant_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("refunds", &refunds), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (reply, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE; + } + num_refunds = json_array_size (refunds); + { + struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (num_refunds)]; + json_t *ercp[GNUNET_NZL (num_refunds)]; + + memset (rds, + 0, + sizeof (rds)); + memset (ercp, + 0, + sizeof (ercp)); + for (unsigned int i = 0; i<num_refunds; i++) + { + struct TALER_MERCHANT_RefundDetail *rd = &rds[i]; + json_t *refund = json_array_get (refunds, i); + uint32_t hs; + struct GNUNET_JSON_Specification spec_detail[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rd->coin_pub), + TALER_JSON_spec_amount ("refund_amount", + &rd->refund_amount), + TALER_JSON_spec_amount ("refund_fee", + &rd->refund_fee), + GNUNET_JSON_spec_uint32 ("exchange_http_status", + &hs), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rd->rtransaction_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (refund, + spec_detail, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE; + } + rd->hr.http_status = (unsigned int) hs; + } + + for (unsigned int i = 0; i<num_refunds; i++) + { + struct TALER_MERCHANT_RefundDetail *rd = &rds[i]; + json_t *refund = json_array_get (refunds, i); + + if (MHD_HTTP_OK == rd->hr.http_status) + { + struct GNUNET_JSON_Specification spec_detail[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rd->exchange_pub), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rd->exchange_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (refund, + spec_detail, + NULL, NULL)) + { + GNUNET_break_op (0); + for (unsigned int j = 0; j<i; j++) + if (NULL != ercp[j]) + json_decref (ercp[j]); + GNUNET_JSON_parse_free (spec); + return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE; + } + /* verify exchange sig (we should not trust the merchant) */ + { + struct TALER_RefundConfirmationPS depconf = { + .purpose.size = htonl (sizeof (depconf)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND), + .h_contract_terms = h_contract_terms, + .coin_pub = rd->coin_pub, + .merchant = merchant_pub, + .rtransaction_id = GNUNET_htonll (rd->rtransaction_id) + }; + + TALER_amount_hton (&depconf.refund_amount, + &rd->refund_amount); + TALER_amount_hton (&depconf.refund_fee, + &rd->refund_fee); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND, + &depconf, + &rd->exchange_sig.eddsa_signature, + &rd->exchange_pub.eddsa_pub)) + { + /* While the *exchange* signature is invalid, we do blame the + merchant here, because the merchant should have checked and + sent us an error code (with exchange HTTP status code 0) instead + of claiming that the exchange yielded a good response. */// + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE; + } + } + } + else + { + uint32_t ec; + struct GNUNET_JSON_Specification spec_detail[] = { + GNUNET_JSON_spec_uint32 ("exchange_code", + &ec), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (refund, + spec_detail, + NULL, NULL)) + { + GNUNET_break_op (0); + rd->hr.ec = TALER_EC_INVALID; + } + ercp[i] = json_incref (json_object_get (refund, + "exchange_reply")); + rd->hr.reply = ercp[i]; + } + } + { + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = MHD_HTTP_OK, + .reply = reply + }; + rlo->cb (rlo->cb_cls, + &hr, + &h_contract_terms, + &merchant_pub, + num_refunds, + rds); + } + for (unsigned int j = 0; j<num_refunds; j++) + if (NULL != ercp[j]) + json_decref (ercp[j]); + } + GNUNET_JSON_parse_free (spec); + return TALER_EC_NONE; +} + + +/** * Process GET /refund response * * @param cls a `struct TALER_MERCHANT_RefundLookupOperation *` @@ -109,7 +286,15 @@ handle_refund_lookup_finished (void *cls, hr.ec = TALER_EC_INVALID_RESPONSE; break; case MHD_HTTP_OK: - /* nothing to do, all good! */ + if (TALER_EC_NONE == + (hr.ec = check_refund_result (rlo, + json))) + { + TALER_MERCHANT_refund_lookup_cancel (rlo); + return; + } + /* failure, report! */ + hr.http_status = 0; break; case MHD_HTTP_NOT_FOUND: hr.ec = TALER_JSON_get_error_code (json); @@ -122,7 +307,11 @@ handle_refund_lookup_finished (void *cls, break; } rlo->cb (rlo->cb_cls, - &hr); + &hr, + NULL, + NULL, + 0, + NULL); TALER_MERCHANT_refund_lookup_cancel (rlo); } diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c @@ -100,6 +100,11 @@ static struct GNUNET_CONTAINER_MultiHashMap *interned_strings; #define USER_ACCOUNT_NAME "62" /** + * Account number of some other user. + */ +#define USER_ACCOUNT_NAME2 "63" + +/** * Account number used by the merchant */ #define MERCHANT_ACCOUNT_NAME "3" @@ -232,7 +237,8 @@ run (void *cls, * Make a reserve exist, * according to the previous * transfer. - */cmd_exec_wirewatch ("wirewatch-1"), + */// + cmd_exec_wirewatch ("wirewatch-1"), TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2", "EUR:10.02", payer_payto, @@ -445,24 +451,70 @@ run (void *cls, }; struct TALER_TESTING_Command refund[] = { - TALER_TESTING_cmd_refund_increase ("refund-increase-1", + cmd_transfer_to_exchange ("create-reserve-1r", + "EUR:10.02"), + /** + * Make a reserve exist, according to the previous transfer. + */// + cmd_exec_wirewatch ("wirewatch-1r"), + TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2r", + "EUR:10.02", + payer_payto, + exchange_payto, + "create-reserve-1r"), + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1r", + "create-reserve-1r", + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2r", + "create-reserve-1r", + "EUR:5", + MHD_HTTP_OK), + /** + * Check the reserve is depleted. + */ + TALER_TESTING_cmd_status ("withdraw-status-1r", + "create-reserve-1r", + "EUR:0", + MHD_HTTP_OK), + TALER_TESTING_cmd_proposal ("create-proposal-1r", + merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ + \"order_id\":\"1r\",\ + \"refund_deadline\": {\"t_ms\": 0},\ + \"pay_deadline\": {\"t_ms\": \"never\" },\ + \"amount\":\"EUR:5.0\",\ + \"summary\": \"merchant-lib testcase\",\ + \"fulfillment_url\": \"https://example.com/\",\ + \"products\": [ {\"description\":\"ice cream\",\ + \"value\":\"{EUR:5}\"} ] }"), + TALER_TESTING_cmd_pay ("pay-for-refund-1r", + merchant_url, + MHD_HTTP_OK, + "create-proposal-1r", + "withdraw-coin-1r", + "EUR:5", + "EUR:4.99", + "EUR:0.01"), + TALER_TESTING_cmd_refund_increase ("refund-increase-1r", merchant_url, "refund test", - "1", /* order ID */ + "1r", /* order ID */ "EUR:0.1", "EUR:0.01", MHD_HTTP_OK), /* Ordinary refund. */ - TALER_TESTING_cmd_refund_lookup ("refund-lookup-1", + TALER_TESTING_cmd_refund_lookup ("refund-lookup-1r", merchant_url, - "refund-increase-1", - "deposit-simple", - "1", + "refund-increase-1r", + "pay-for-refund-1r", + "1r", MHD_HTTP_OK), /* Trying to pick up refund from non existent proposal. */ TALER_TESTING_cmd_refund_lookup ("refund-lookup-non-existent", merchant_url, - "refund-increase-1", + "refund-increase-1r", "deposit-simple", "non-existend-id", MHD_HTTP_NOT_FOUND), @@ -522,7 +574,7 @@ run (void *cls, "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"unincreased-proposal\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":9999999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"amount\":\"EUR:5.0\",\ \"summary\": \"merchant-lib testcase\",\ \"fulfillment_url\": \"https://example.com/\",\ @@ -538,19 +590,19 @@ run (void *cls, "EUR:0.01"), CMD_EXEC_AGGREGATOR ("run-aggregator-unincreased-refund"), TALER_TESTING_cmd_check_bank_transfer ( - "check_bank_transfer-unincreased-refund", + "check_bank_transfer-paid-unincreased-refund", EXCHANGE_URL, - "EUR:4.98", + "EUR:9.88", /* '4.98 from above', plus 4.99 from 'pay-for-refund-1r' + and MINUS 0.1 PLUS 0.01 (deposit fee) from 'refund-increase-1r' */ exchange_payto, merchant_payto), - /* Actually try to pick up the refund from the - * "unincreased proposal". */ + /* Actually try to pick up the refund from the "unincreased proposal". */ TALER_TESTING_cmd_refund_lookup_with_amount ("refund-lookup-unincreased", merchant_url, NULL, "pay-unincreased-proposal", "unincreased-proposal", - MHD_HTTP_OK, + MHD_HTTP_NOT_FOUND, /* If a lookup is attempted * on an unincreased * proposal, the backend will @@ -802,7 +854,7 @@ run (void *cls, \"value\":\"{EUR:10}\"} ] }"), TALER_TESTING_cmd_pay ("pay-fail-partial-double-11-good", merchant_url, - MHD_HTTP_BAD_REQUEST, + MHD_HTTP_NOT_ACCEPTABLE, "create-proposal-11", "withdraw-coin-11a", "EUR:5", @@ -865,7 +917,7 @@ run (void *cls, merchant_url, MHD_HTTP_OK, GNUNET_TIME_UNIT_ZERO_ABS, - 4, /* Expected number of records */ + 5, /* Expected number of records */ -100), /* Delta */ /** * End the suite. Fixme: better to have a label for this diff --git a/src/lib/test_merchant_api_twisted.c b/src/lib/test_merchant_api_twisted.c @@ -152,16 +152,14 @@ CMD_EXEC_WIREWATCH (const char *label) /** - * Execute the taler-exchange-aggregator command with + * Execute the taler-exchange-aggregator, closer and transfer commands with * our configuration file. * * @param label label to use for the command. */ -static struct TALER_TESTING_Command -CMD_EXEC_AGGREGATOR (const char *label) -{ - return TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE); -} +#define CMD_EXEC_AGGREGATOR(label) \ + TALER_TESTING_cmd_exec_aggregator (label "-aggregator", CONFIG_FILE), \ + TALER_TESTING_cmd_exec_transfer (label "-transfer", CONFIG_FILE) /** @@ -173,7 +171,8 @@ CMD_EXEC_AGGREGATOR (const char *label) * @param url exchange_url */ static struct TALER_TESTING_Command -CMD_TRANSFER_TO_EXCHANGE (const char *label, const char *amount) +CMD_TRANSFER_TO_EXCHANGE (const char *label, + const char *amount) { return TALER_TESTING_cmd_admin_add_incoming (label, amount, @@ -192,10 +191,8 @@ static void run (void *cls, struct TALER_TESTING_Interpreter *is) { - /**** Triggering #5719 ****/ struct TALER_TESTING_Command bug_5719[] = { - /** * Move money to the exchange's bank account. */ @@ -206,30 +203,26 @@ run (void *cls, * transfer. */ CMD_EXEC_WIREWATCH ("5719-wirewatch"), - TALER_TESTING_cmd_check_bank_admin_transfer - ("5719-check-transfer", - "EUR:1.01", - payer_payto, - exchange_payto, - "5719-create-reserve"), - + TALER_TESTING_cmd_check_bank_admin_transfer ("5719-check-transfer", + "EUR:1.01", + payer_payto, + exchange_payto, + "5719-create-reserve"), TALER_TESTING_cmd_withdraw_amount ("5719-withdraw", "5719-create-reserve", "EUR:1", MHD_HTTP_OK), - TALER_TESTING_cmd_status ("5719-reserve-status", "5719-create-reserve", "EUR:0", MHD_HTTP_OK), - TALER_TESTING_cmd_proposal - ("5719-create-proposal", - twister_merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_proposal ("5719-create-proposal", + twister_merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"5719TRIGGER\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"fulfillment_url\": \"https://example.com/\",\ \"amount\":\"EUR:1.0\",\ \"summary\": \"merchant-lib testcase\",\ @@ -242,13 +235,12 @@ run (void *cls, * not manage to pass the callback a valid JSON and will * instead pass a NULL pointer. This should trigger the path * mentioned in the bug report #5719. - */TALER_TESTING_cmd_malform_response - ("5719-malform-exchange-resp", - PROXY_EXCHANGE_CONFIG_FILE), - + */// + TALER_TESTING_cmd_malform_response ("5719-malform-exchange-resp", + PROXY_EXCHANGE_CONFIG_FILE), TALER_TESTING_cmd_pay ("5719-deposit", twister_merchant_url, - MHD_HTTP_SERVICE_UNAVAILABLE, + MHD_HTTP_FAILED_DEPENDENCY, "5719-create-proposal", "5719-withdraw", "EUR:1", @@ -268,7 +260,7 @@ run (void *cls, "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"fail-check-payment-1\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"fulfillment_url\": \"https://example.com/\",\ \"amount\":\"EUR:2.0\",\ \"summary\": \"merchant-lib testcase\",\ @@ -276,41 +268,31 @@ run (void *cls, \"value\":\"EUR:3\"} ] }"), /* Need any response code != 200. */ - TALER_TESTING_cmd_hack_response_code - ("non-200-response-code", - PROXY_MERCHANT_CONFIG_FILE, - MHD_HTTP_MULTIPLE_CHOICES), - - TALER_TESTING_cmd_check_payment - ("check-payment-fail", - twister_merchant_url, - MHD_HTTP_MULTIPLE_CHOICES, - "proposal-for-check-payment", - GNUNET_SYSERR), // any response != 200 gives "syserr" - + TALER_TESTING_cmd_hack_response_code ("non-200-response-code", + PROXY_MERCHANT_CONFIG_FILE, + MHD_HTTP_MULTIPLE_CHOICES), + TALER_TESTING_cmd_check_payment ("check-payment-fail", + twister_merchant_url, + MHD_HTTP_MULTIPLE_CHOICES, + "proposal-for-check-payment", + GNUNET_SYSERR), // any response != 200 gives "syserr" TALER_TESTING_cmd_delete_object ("hack-check-payment-0", PROXY_MERCHANT_CONFIG_FILE, "taler_pay_uri"), - TALER_TESTING_cmd_check_payment - ("check-payment-fail-invalid", - twister_merchant_url, - 0, - "proposal-for-check-payment", - GNUNET_SYSERR), - - TALER_TESTING_cmd_modify_object_dl - ("paid-true-for-unpaid", - PROXY_MERCHANT_CONFIG_FILE, - "paid", - "true"), - - TALER_TESTING_cmd_check_payment - ("check-payment-fail-invalid-0", - twister_merchant_url, - 0, - "proposal-for-check-payment", - GNUNET_SYSERR), - + TALER_TESTING_cmd_check_payment ("check-payment-fail-invalid", + twister_merchant_url, + 0, + "proposal-for-check-payment", + GNUNET_SYSERR), + TALER_TESTING_cmd_modify_object_dl ("paid-true-for-unpaid", + PROXY_MERCHANT_CONFIG_FILE, + "paid", + "true"), + TALER_TESTING_cmd_check_payment ("check-payment-fail-invalid-0", + twister_merchant_url, + 0, + "proposal-for-check-payment", + GNUNET_SYSERR), TALER_TESTING_cmd_end () }; @@ -321,49 +303,41 @@ run (void *cls, * Make the merchant return a 400 Bad Request response * due to uploaded body malformation. */ - TALER_TESTING_cmd_malform_request - ("malform-order", - PROXY_MERCHANT_CONFIG_FILE), - - TALER_TESTING_cmd_proposal - ("create-proposal-0", - twister_merchant_url, - MHD_HTTP_BAD_REQUEST, - /* giving a valid JSON to not make it fail before - * data reaches the merchant. */ - "{\"not\": \"used\"}"), - - TALER_TESTING_cmd_hack_response_code - ("proposal-500", - PROXY_MERCHANT_CONFIG_FILE, - MHD_HTTP_INTERNAL_SERVER_ERROR), - - TALER_TESTING_cmd_proposal - ("create-proposal-1", - twister_merchant_url, - /* This status code == 0 is gotten via a 500 Internal Server - * Error handed to the library. */ - MHD_HTTP_INTERNAL_SERVER_ERROR, - /* giving a valid JSON to not make it fail before - * data reaches the merchant. */ - "{\"not\": \"used\"}"), + TALER_TESTING_cmd_malform_request ("malform-order", + PROXY_MERCHANT_CONFIG_FILE), + TALER_TESTING_cmd_proposal ("create-proposal-0", + twister_merchant_url, + MHD_HTTP_BAD_REQUEST, + /* giving a valid JSON to not make it fail before + * data reaches the merchant. */ + "{\"not\": \"used\"}"), + TALER_TESTING_cmd_hack_response_code ("proposal-500", + PROXY_MERCHANT_CONFIG_FILE, + MHD_HTTP_INTERNAL_SERVER_ERROR), + TALER_TESTING_cmd_proposal ("create-proposal-1", + twister_merchant_url, + /* This status code == 0 is gotten via a 500 Internal Server + * Error handed to the library. */ + MHD_HTTP_INTERNAL_SERVER_ERROR, + /* giving a valid JSON to not make it fail before + * data reaches the merchant. */ + "{\"not\": \"used\"}"), /** * Cause the PUT /proposal callback to be called * with a response code == 0. We achieve this by malforming * the response body. - */TALER_TESTING_cmd_malform_response - ("malform-proposal", - PROXY_MERCHANT_CONFIG_FILE), - - TALER_TESTING_cmd_proposal - ("create-proposal-2", - twister_merchant_url, - 0, - "{\"max_fee\":\"EUR:0.5\",\ + */// + TALER_TESTING_cmd_malform_response ("malform-proposal", + PROXY_MERCHANT_CONFIG_FILE), + + TALER_TESTING_cmd_proposal ("create-proposal-2", + twister_merchant_url, + 0, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"1\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"amount\":\"EUR:5.0\",\ \"summary\": \"merchant-lib testcase\",\ \"products\": [ {\"description\":\"ice cream\",\ @@ -375,15 +349,14 @@ run (void *cls, TALER_TESTING_cmd_delete_object ("remove-order-id", PROXY_MERCHANT_CONFIG_FILE, "order_id"), - TALER_TESTING_cmd_proposal - ("create-proposal-3", - twister_merchant_url, - 0, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_proposal ("create-proposal-3", + twister_merchant_url, + 0, + "{\"max_fee\":\"EUR:0.5\",\ \"fulfillment_url\": \"https://example.com/\",\ \"order_id\":\"2\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"amount\":\"EUR:5.0\",\ \"summary\": \"merchant-lib testcase\",\ \"products\": [ {\"description\":\"ice cream\",\ @@ -392,11 +365,10 @@ run (void *cls, * Cause a 404 Not Found response code, * due to a non existing merchant instance. */ - TALER_TESTING_cmd_proposal - ("create-proposal-4", - twister_merchant_url_instance_nonexistent, - MHD_HTTP_NOT_FOUND, - "{\"amount\":\"EUR:5\",\ + TALER_TESTING_cmd_proposal ("create-proposal-4", + twister_merchant_url_instance_nonexistent, + MHD_HTTP_NOT_FOUND, + "{\"amount\":\"EUR:5\",\ \"fulfillment_url\": \"https://example.com/\",\ \"summary\": \"merchant-lib testcase\"}"), @@ -426,14 +398,13 @@ run (void *cls, /* First step is to create a _valid_ proposal, so that * we can lookup for it later. */ - TALER_TESTING_cmd_proposal - ("create-proposal-5", - twister_merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_proposal ("create-proposal-5", + twister_merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"5\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"amount\":\"EUR:5.0\",\ \"fulfillment_url\": \"https://example.com/\",\ \"summary\": \"merchant-lib testcase\",\ @@ -464,14 +435,13 @@ run (void *cls, * code, that is then expected to trigger some * emergency behaviour, like setting the response * code to zero before calling the callback. - */TALER_TESTING_cmd_hack_response_code - ("twist-history", - PROXY_MERCHANT_CONFIG_FILE, - MHD_HTTP_GONE), - + */// + TALER_TESTING_cmd_hack_response_code ("twist-history", + PROXY_MERCHANT_CONFIG_FILE, + MHD_HTTP_GONE), TALER_TESTING_cmd_history ("history-0", twister_merchant_url, - 0, + MHD_HTTP_GONE, GNUNET_TIME_UNIT_ZERO_ABS, 1, // nresult 10, // start @@ -480,9 +450,9 @@ run (void *cls, * Making the returned response malformed, in order * to make the JSON downloader+parser fail and call * the lib passing a response code as zero. - */TALER_TESTING_cmd_malform_response - ("malform-history", - PROXY_MERCHANT_CONFIG_FILE), + */// + TALER_TESTING_cmd_malform_response ("malform-history", + PROXY_MERCHANT_CONFIG_FILE), TALER_TESTING_cmd_history ("history-1", twister_merchant_url, @@ -500,64 +470,49 @@ run (void *cls, * This block tests whether a refund_deadline and/or * wire_transfer_deadline very far in the future do NOT * result in any wire transfer from the aggregator (#5366). - */struct TALER_TESTING_Command unaggregation[] = { - - CMD_TRANSFER_TO_EXCHANGE - ("create-reserve-unaggregation", - "EUR:5.01"), - - CMD_EXEC_WIREWATCH - ("wirewatch-unaggregation"), - TALER_TESTING_cmd_check_bank_admin_transfer - ("check_bank_transfer-unaggregation", + */// + struct TALER_TESTING_Command unaggregation[] = { + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregation", + "EUR:5.01"), + CMD_EXEC_WIREWATCH ("wirewatch-unaggregation"), + TALER_TESTING_cmd_check_bank_admin_transfer ( + "check_bank_transfer-unaggregation", "EUR:5.01", payer_payto, exchange_payto, "create-reserve-unaggregation"), - - TALER_TESTING_cmd_check_bank_empty - ("check_bank_unaggregated-a"), - - TALER_TESTING_cmd_withdraw_amount - ("withdraw-coin-unaggregation", - "create-reserve-unaggregation", - "EUR:5", - MHD_HTTP_OK), - - TALER_TESTING_cmd_proposal - ("create-proposal-unaggregation", - /* Need a fresh instance in order to associate this - * proposal with a fresh h_wire; this way, this proposal - * won't get hooked by the aggregator gathering same-h_wire'd - * transactions. */ - twister_merchant_url_instance_tor, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_check_bank_empty ("check_bank_unaggregated-a"), + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregation", + "create-reserve-unaggregation", + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_proposal ("create-proposal-unaggregation", + /* Need a fresh instance in order to associate this + * proposal with a fresh h_wire; this way, this proposal + * won't get hooked by the aggregator gathering same-h_wire'd + * transactions. */ + twister_merchant_url_instance_tor, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"refund_deadline\":{\"t_ms\":2000},\ - \"pay_deadline\":{\"t_ms\":1000},\ + \"pay_deadline\":{\"t_ms\":2366841500000},\ \"wire_transfer_deadline\":{\"t_ms\":2366841600000},\ \"amount\":\"EUR:0.5\",\ \"summary\": \"unaggregated product\",\ \"fulfillment_url\": \"https://example.com/\",\ \"products\": [ {\"description\":\"unaggregated cream\",\ \"value\":\"{EUR:5}\"} ] }"), - - TALER_TESTING_cmd_pay - ("pay-unaggregation", - twister_merchant_url_instance_tor, - MHD_HTTP_OK, - "create-proposal-unaggregation", - "withdraw-coin-unaggregation", - "EUR:5", // amount + fee - "EUR:4.99", // amount - fee - "EUR:0.01"), // refund fee - - CMD_EXEC_AGGREGATOR - ("aggregation-attempt"), - + TALER_TESTING_cmd_pay ("pay-unaggregation", + twister_merchant_url_instance_tor, + MHD_HTTP_OK, + "create-proposal-unaggregation", + "withdraw-coin-unaggregation", + "EUR:5", // amount + fee + "EUR:4.99", // amount - fee + "EUR:0.01"), // refund fee + CMD_EXEC_AGGREGATOR ("aggregation-attempt"), /* Make sure NO aggregation took place. */ - TALER_TESTING_cmd_check_bank_empty - ("check_bank_unaggregated-b"), + TALER_TESTING_cmd_check_bank_empty ("check_bank_unaggregated-b"), TALER_TESTING_cmd_end () }; @@ -567,30 +522,26 @@ run (void *cls, CMD_TRANSFER_TO_EXCHANGE ("create-reserve-5383", "EUR:2.02"), CMD_EXEC_WIREWATCH ("wirewatch-5383"), - TALER_TESTING_cmd_check_bank_admin_transfer - ("check_bank_transfer-5383", - "EUR:2.02", - payer_payto, - exchange_payto, - "create-reserve-5383"), - TALER_TESTING_cmd_withdraw_amount - ("withdraw-coin-5383a", - "create-reserve-5383", - "EUR:1", - MHD_HTTP_OK), - TALER_TESTING_cmd_withdraw_amount - ("withdraw-coin-5383b", - "create-reserve-5383", - "EUR:1", - MHD_HTTP_OK), - TALER_TESTING_cmd_proposal - ("create-proposal-5383", - twister_merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-5383", + "EUR:2.02", + payer_payto, + exchange_payto, + "create-reserve-5383"), + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-5383a", + "create-reserve-5383", + "EUR:1", + MHD_HTTP_OK), + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-5383b", + "create-reserve-5383", + "EUR:1", + MHD_HTTP_OK), + TALER_TESTING_cmd_proposal ("create-proposal-5383", + twister_merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"5383\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"fulfillment_url\": \"https://example.com/\",\ \"amount\":\"EUR:2.0\",\ \"summary\": \"merchant-lib testcase\",\ @@ -606,27 +557,23 @@ run (void *cls, "EUR:1.99", // no sense now "EUR:0.01"), // no sense now CMD_EXEC_AGGREGATOR ("run-aggregator-5383"), - TALER_TESTING_cmd_check_bank_transfer - ("check_aggregation_transfer-5383", - twister_exchange_url, - /* paid, 1.97 = - brutto 2.00 - - deposit fee 0.01 * 2 - - wire fee 0.01 - */"EUR:1.97", - exchange_payto, - merchant_payto), - TALER_TESTING_cmd_modify_object_dl - ("hack-5383", - PROXY_EXCHANGE_CONFIG_FILE, - "total", - "EUR:0.98"), - TALER_TESTING_cmd_merchant_track_transfer - ("track-5383", - twister_merchant_url, - MHD_HTTP_FAILED_DEPENDENCY, - "check_aggregation_transfer-5383"), - + TALER_TESTING_cmd_check_bank_transfer ("check_aggregation_transfer-5383", + twister_exchange_url, + /* paid, 1.97 = + brutto 2.00 - + deposit fee 0.01 * 2 - + wire fee 0.01 + */"EUR:1.97", + exchange_payto, + merchant_payto), + TALER_TESTING_cmd_modify_object_dl ("hack-5383", + PROXY_EXCHANGE_CONFIG_FILE, + "total", + "EUR:0.98"), + TALER_TESTING_cmd_merchant_track_transfer ("track-5383", + twister_merchant_url, + MHD_HTTP_FAILED_DEPENDENCY, + "check_aggregation_transfer-5383"), TALER_TESTING_cmd_end () }; @@ -645,17 +592,12 @@ run (void *cls, * transfer. */ CMD_EXEC_WIREWATCH ("wirewatch-1"), - - TALER_TESTING_cmd_check_bank_admin_transfer - ("check_bank_transfer-2", - "EUR:2.02", - payer_payto, - exchange_payto, - "create-reserve-1"), - - TALER_TESTING_cmd_check_bank_empty - ("track_chunk_check_empty-a"), - + TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2", + "EUR:2.02", + payer_payto, + exchange_payto, + "create-reserve-1"), + TALER_TESTING_cmd_check_bank_empty ("track_chunk_check_empty-a"), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", "EUR:1", @@ -668,15 +610,13 @@ run (void *cls, "create-reserve-1", "EUR:0", MHD_HTTP_OK), - - TALER_TESTING_cmd_proposal - ("create-proposal-6", - twister_merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_proposal ("create-proposal-6", + twister_merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"11\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"fulfillment_url\": \"https://example.com/\",\ \"amount\":\"EUR:2.0\",\ \"summary\": \"merchant-lib testcase\",\ @@ -688,7 +628,6 @@ run (void *cls, MHD_HTTP_OK, "create-proposal-6", GNUNET_NO), - TALER_TESTING_cmd_pay ("deposit-simple", twister_merchant_url, MHD_HTTP_OK, @@ -698,23 +637,22 @@ run (void *cls, "EUR:2", "EUR:1.99", // no sense now "EUR:0.01"), // no sense now - TALER_TESTING_cmd_check_payment ("check-payment-2", twister_merchant_url, MHD_HTTP_OK, "create-proposal-6", GNUNET_YES), CMD_EXEC_AGGREGATOR ("run-aggregator"), - TALER_TESTING_cmd_check_bank_transfer - ("check_bank_transfer-1", - twister_exchange_url, /* has the 8888-port thing. */ - /* paid, 1.97 = - brutto 2.00 - - deposit fee 0.01 * 2 - - wire fee 0.01 - */"EUR:1.97", - exchange_payto, - merchant_payto), + TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-1", + twister_exchange_url, /* has the 8888-port thing. */ + /* paid, 1.97 = + brutto 2.00 - + deposit fee 0.01 * 2 - + wire fee 0.01 + */// + "EUR:1.97", + exchange_payto, + merchant_payto), /** * Fake total to include only one coin. Math: each 1-EUR @@ -727,21 +665,18 @@ run (void *cls, * In particular, they are supposed to modify the call * to /track/transfer issued from the merchant to the * exchange that happens _before_ the call to /track/transaction - * issued below by the test case (to the merchant backend.) */TALER_TESTING_cmd_modify_object_dl - ("hack-0", - PROXY_EXCHANGE_CONFIG_FILE, - "total", - "EUR:0.98"), - TALER_TESTING_cmd_delete_object - ("hack-1", - PROXY_EXCHANGE_CONFIG_FILE, - "deposits.0"), - TALER_TESTING_cmd_merchant_track_transaction - ("track-transaction-1", - twister_merchant_url, - MHD_HTTP_FAILED_DEPENDENCY, - "deposit-simple"), - + * issued below by the test case (to the merchant backend.) */// + TALER_TESTING_cmd_modify_object_dl ("hack-0", + PROXY_EXCHANGE_CONFIG_FILE, + "total", + "EUR:0.98"), + TALER_TESTING_cmd_delete_object ("hack-1", + PROXY_EXCHANGE_CONFIG_FILE, + "deposits.0"), + TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-1", + twister_merchant_url, + MHD_HTTP_FAILED_DEPENDENCY, + "deposit-simple"), TALER_TESTING_cmd_end () }; @@ -756,42 +691,34 @@ run (void *cls, "EUR:1.01"), /** - * Make a reserve exist, according to the previous - * transfer. + * Make a reserve exist, according to the previous transfer. */ CMD_EXEC_WIREWATCH ("wirewatch-abort-1"), - - TALER_TESTING_cmd_check_bank_admin_transfer - ("check_bank_transfer-abort-1", - "EUR:1.01", - payer_payto, - exchange_payto, - "create-reserve-abort-1"), - + TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-abort-1", + "EUR:1.01", + payer_payto, + exchange_payto, + "create-reserve-abort-1"), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-abort-1", "create-reserve-abort-1", "EUR:1", MHD_HTTP_OK), - TALER_TESTING_cmd_status ("withdraw-status-abort-1", "create-reserve-abort-1", "EUR:0", MHD_HTTP_OK), - - TALER_TESTING_cmd_proposal - ("create-proposal-abort-1", - twister_merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_proposal ("create-proposal-abort-1", + twister_merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"abort-one\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"fulfillment_url\": \"https://example.com/\",\ \"amount\":\"EUR:3.0\",\ \"summary\": \"merchant-lib testcase\",\ \"products\": [ {\"description\":\"ice cream\",\ \"value\":\"{EUR:3}\"} ] }"), - /* Will only pay _half_ the supposed price, * so we'll then have the right to abort. */ TALER_TESTING_cmd_pay ("deposit-simple-for-abort", @@ -802,94 +729,73 @@ run (void *cls, "EUR:1", "EUR:1.99", // no sense now "EUR:0.01"), // no sense now - TALER_TESTING_cmd_delete_object ("hack-abort-1", PROXY_MERCHANT_CONFIG_FILE, "merchant_pub"), - TALER_TESTING_cmd_pay_abort ("pay-abort-1", twister_merchant_url, "deposit-simple-for-abort", 0), - - TALER_TESTING_cmd_delete_object - ("hack-abort-2", - PROXY_MERCHANT_CONFIG_FILE, - "refund_permissions.0.rtransaction_id"), - + TALER_TESTING_cmd_delete_object ("hack-abort-2", + PROXY_MERCHANT_CONFIG_FILE, + "refund_permissions.0.rtransaction_id"), TALER_TESTING_cmd_pay_abort ("pay-abort-2", twister_merchant_url, "deposit-simple-for-abort", 0), - - TALER_TESTING_cmd_modify_object_dl - ("hack-abort-3", - PROXY_MERCHANT_CONFIG_FILE, - "refund_permissions.0.coin_pub", - /* dummy coin. */ - "8YX10E41ZWHX0X2RK4XFAXB2D3M05M1HNG14ZFZZB8M7SA4QCKCG"), - + TALER_TESTING_cmd_modify_object_dl ("hack-abort-3", + PROXY_MERCHANT_CONFIG_FILE, + "refund_permissions.0.coin_pub", + /* dummy coin. */ + "8YX10E41ZWHX0X2RK4XFAXB2D3M05M1HNG14ZFZZB8M7SA4QCKCG"), TALER_TESTING_cmd_pay_abort ("pay-abort-3", twister_merchant_url, "deposit-simple-for-abort", 0), - - TALER_TESTING_cmd_flip_download - ("hack-abort-4", - PROXY_MERCHANT_CONFIG_FILE, - "refund_permissions.0.merchant_sig"), - + TALER_TESTING_cmd_flip_download ("hack-abort-4", + PROXY_MERCHANT_CONFIG_FILE, + "refund_permissions.0.merchant_sig"), TALER_TESTING_cmd_pay_abort ("pay-abort-4", twister_merchant_url, "deposit-simple-for-abort", 0), /* just malforming the response. */ - TALER_TESTING_cmd_malform_response - ("malform-abortion", - PROXY_MERCHANT_CONFIG_FILE), - + TALER_TESTING_cmd_malform_response ("malform-abortion", + PROXY_MERCHANT_CONFIG_FILE), TALER_TESTING_cmd_pay_abort ("pay-abort-5", twister_merchant_url, "deposit-simple-for-abort", 0), - CMD_TRANSFER_TO_EXCHANGE ("create-reserve-double-spend", "EUR:1.01"), - CMD_EXEC_WIREWATCH ("wirewatch-double-spend"), - - TALER_TESTING_cmd_proposal - ("create-proposal-double-spend", - twister_merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_proposal ("create-proposal-double-spend", + twister_merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"DS-1\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"fulfillment_url\": \"https://example.com/\",\ \"amount\":\"EUR:1.0\",\ \"summary\": \"merchant-lib testcase\",\ \"products\": [ {\"description\": \"will succeed\"}] }"), - - TALER_TESTING_cmd_proposal - ("create-proposal-double-spend-1", - twister_merchant_url, - MHD_HTTP_OK, - "{\"max_fee\":\"EUR:0.5\",\ + TALER_TESTING_cmd_proposal ("create-proposal-double-spend-1", + twister_merchant_url, + MHD_HTTP_OK, + "{\"max_fee\":\"EUR:0.5\",\ \"order_id\":\"DS-2\",\ \"refund_deadline\":{\"t_ms\":0},\ - \"pay_deadline\":{\"t_ms\":99999999999},\ + \"pay_deadline\":{\"t_ms\":\"never\"},\ \"fulfillment_url\": \"https://example.com/\",\ \"amount\":\"EUR:1.0\",\ \"summary\": \"merchant-lib testcase\",\ \"products\": [ {\"description\": \"will fail\"}] }"), - TALER_TESTING_cmd_withdraw_amount - ("withdraw-coin-double-spend", - "create-reserve-double-spend", - "EUR:1", - MHD_HTTP_OK), - + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-double-spend", + "create-reserve-double-spend", + "EUR:1", + MHD_HTTP_OK), TALER_TESTING_cmd_pay ("deposit-simple-ok", twister_merchant_url, MHD_HTTP_OK, @@ -898,57 +804,40 @@ run (void *cls, "EUR:1", "EUR:1.99", // no sense now "EUR:0.01"), // no sense now - - TALER_TESTING_cmd_flip_download - ("hack-coin-history", - PROXY_MERCHANT_CONFIG_FILE, - "history.0.coin_sig"), - + TALER_TESTING_cmd_flip_download ("hack-coin-history", + PROXY_MERCHANT_CONFIG_FILE, + "history.0.coin_sig"), /* Coin history check will fail, * due to coin's bad signature. */ TALER_TESTING_cmd_pay ("deposit-simple-fail", twister_merchant_url, - 0, + MHD_HTTP_CONFLICT, "create-proposal-double-spend-1", "withdraw-coin-double-spend", "EUR:1", "EUR:1.99", // no sense now "EUR:0.01"), // no sense now - /* max uint64 number: 9223372036854775807; try to overflow! */ - TALER_TESTING_cmd_end () }; struct TALER_TESTING_Command commands[] = { - TALER_TESTING_cmd_batch ("check-payment", check_payment), - TALER_TESTING_cmd_batch ("proposal", proposal), - TALER_TESTING_cmd_batch ("history", history), - TALER_TESTING_cmd_batch ("unaggregation", unaggregation), - TALER_TESTING_cmd_batch ("track", track), - TALER_TESTING_cmd_batch ("track-5383", track_5383), - TALER_TESTING_cmd_batch ("pay", pay), - TALER_TESTING_cmd_batch ("bug-5719", bug_5719), - /** - * End the suite. Fixme: better to have a label for this - * too, as it shows a "(null)" token on logs. - */ TALER_TESTING_cmd_end () }; diff --git a/src/lib/testing_api_cmd_check_payment.c b/src/lib/testing_api_cmd_check_payment.c @@ -418,6 +418,7 @@ check_payment_conclude_cleanup (void *cls, GNUNET_SCHEDULER_cancel (cps->task); cps->task = NULL; } + GNUNET_free (cps); } diff --git a/src/lib/testing_api_cmd_history.c b/src/lib/testing_api_cmd_history.c @@ -109,10 +109,9 @@ history_cb (void *cls, if (hs->http_status != hr->http_status) TALER_TESTING_FAIL (hs->is); - if (0 == hs->http_status) + if (MHD_HTTP_OK != hs->http_status) { - /* 0 was caused intentionally by the tests, - * move on without further checking. */ + /* move on without further checking. */ TALER_TESTING_interpreter_next (hs->is); return; } @@ -277,15 +276,16 @@ cmd_history2 (const char *label, hs->nrows = nrows; hs->merchant_url = merchant_url; hs->use_default_start = use_default_start; - - struct TALER_TESTING_Command cmd = { - .cls = hs, - .label = label, - .run = &history_run, - .cleanup = &history_cleanup - }; - - return cmd; + { + struct TALER_TESTING_Command cmd = { + .cls = hs, + .label = label, + .run = &history_run, + .cleanup = &history_cleanup + }; + + return cmd; + } } diff --git a/src/lib/testing_api_cmd_pay_abort_refund.c b/src/lib/testing_api_cmd_pay_abort_refund.c @@ -85,11 +85,13 @@ struct PayAbortRefundState * @param cls closure * @param hr HTTP response code details * @param sign_key exchange key used to sign @a obj, or NULL + * @param signature the actual signature, or NULL on error */ static void abort_refund_cb (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, - const struct TALER_ExchangePublicKeyP *sign_key) + const struct TALER_ExchangePublicKeyP *sign_key, + const struct TALER_ExchangeSignatureP *signature) { struct PayAbortRefundState *pars = cls; diff --git a/src/lib/testing_api_cmd_poll_payment.c b/src/lib/testing_api_cmd_poll_payment.c @@ -391,6 +391,7 @@ poll_payment_conclude_cleanup (void *cls, GNUNET_SCHEDULER_cancel (cps->task); cps->task = NULL; } + GNUNET_free (cps); } diff --git a/src/lib/testing_api_cmd_proposal.c b/src/lib/testing_api_cmd_proposal.c @@ -215,8 +215,9 @@ proposal_cb (void *cls, ps->po = NULL; if (ps->http_status != hr->http_status) { - TALER_LOG_ERROR ("Given vs expected: %u vs %u\n", + TALER_LOG_ERROR ("Given vs expected: %u(%d) vs %u\n", hr->http_status, + (int) hr->ec, ps->http_status); TALER_TESTING_FAIL (ps->is); } diff --git a/src/lib/testing_api_cmd_refund_increase.c b/src/lib/testing_api_cmd_refund_increase.c @@ -114,7 +114,14 @@ refund_increase_cb (void *cls, ris->rio = NULL; if (ris->http_code != hr->http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected status %u, got %u(%d) for refund increase\n", + ris->http_code, + hr->http_status, + (int) hr->ec); TALER_TESTING_FAIL (ris->is); + } TALER_TESTING_interpreter_next (ris->is); } diff --git a/src/lib/testing_api_cmd_refund_lookup.c b/src/lib/testing_api_cmd_refund_lookup.c @@ -104,199 +104,209 @@ refund_lookup_cleanup (void *cls, /** - * Callback that frees all the elements in the hashmap - * - * @param cls closure, NULL - * @param key current key - * @param value a `struct TALER_Amount` - * - * @return always #GNUNET_YES (continue to iterate) - */ -static int -hashmap_free (void *cls, - const struct GNUNET_HashCode *key, - void *value) -{ - struct TALER_Amount *refund_amount = value; - - GNUNET_free (refund_amount); - return GNUNET_YES; -} - - -/** * Process "GET /public/refund" (lookup) response; * mainly checking if the refunded amount matches the * expectation. * * @param cls closure * @param hr HTTP response we got + * @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 */ static void refund_lookup_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr) + 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) { struct RefundLookupState *rls = cls; struct GNUNET_CONTAINER_MultiHashMap *map; - size_t index; - json_t *elem; - const char *error_name; - unsigned int error_line; - struct GNUNET_HashCode h_coin_pub; const char *coin_reference; - char *coin_reference_dup; const char *icoin_reference; - const struct TALER_TESTING_Command *pay_cmd; - const struct TALER_TESTING_Command *increase_cmd; const char *refund_amount; struct TALER_Amount acc; struct TALER_Amount ra; - const json_t *arr; rls->rlo = NULL; + if (MHD_HTTP_GONE == rls->http_code) + { + /* special case: GONE is not the top-level code, but expected INSIDE the details */ + if (MHD_HTTP_OK != hr->http_status) + TALER_TESTING_FAIL (rls->is); + for (unsigned int i = 0; i<num_details; i++) + if (MHD_HTTP_GONE != details[i].hr.http_status) + TALER_TESTING_FAIL (rls->is); + /* all good */ + TALER_TESTING_interpreter_next (rls->is); + return; + } if (rls->http_code != hr->http_status) TALER_TESTING_FAIL (rls->is); - - arr = json_object_get (hr->reply, - "refund_permissions"); - if (NULL == arr) + if (MHD_HTTP_OK != hr->http_status) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Tolerating a refund permission not found\n"); TALER_TESTING_interpreter_next (rls->is); return; } map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); - /* Put in array every refunded coin. */ - json_array_foreach (arr, index, elem) + for (unsigned int i = 0; i<num_details; i++) { - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_Amount *irefund_amount = GNUNET_new - (struct TALER_Amount); - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), - TALER_JSON_spec_amount ("refund_amount", irefund_amount), - GNUNET_JSON_spec_end () - }; + struct GNUNET_HashCode h_coin_pub; - GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (elem, - spec, - &error_name, - &error_line)); - GNUNET_CRYPTO_hash (&coin_pub, + if (MHD_HTTP_OK != details[i].hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Got unexpected status %u/%d for refunded coin %u\n", + details[i].hr.http_status, + (int) details[i].hr.ec, + i); + GNUNET_CONTAINER_multihashmap_destroy (map); + TALER_TESTING_FAIL (rls->is); + return; + } + TALER_LOG_DEBUG ("Coin %s refund is %s\n", + TALER_B2S (&details[i].coin_pub), + TALER_amount2s (&details[i].refund_amount)); + GNUNET_CRYPTO_hash (&details[i].coin_pub, sizeof (struct TALER_CoinSpendPublicKeyP), &h_coin_pub); - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_put ( - map, - &h_coin_pub, // which - irefund_amount, // how much - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - }; + if (GNUNET_OK != + GNUNET_CONTAINER_multihashmap_put ( + map, + &h_coin_pub, + (void *) &details[i], + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_CONTAINER_multihashmap_destroy (map); + TALER_TESTING_FAIL (rls->is); + } + } /* Compare spent coins with refunded, and if they match, * increase an accumulator. */ - if (NULL == (pay_cmd = TALER_TESTING_interpreter_lookup_command ( - rls->is, - rls->pay_reference))) - TALER_TESTING_FAIL (rls->is); - - if (GNUNET_OK != - TALER_TESTING_get_trait_coin_reference ( - pay_cmd, - 0, - &coin_reference)) - TALER_TESTING_FAIL (rls->is); - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero ("EUR", - &acc)); - coin_reference_dup = GNUNET_strdup (coin_reference); - for (icoin_reference = strtok (coin_reference_dup, ";"); - NULL != icoin_reference; - icoin_reference = strtok (NULL, ";")) { - const struct TALER_CoinSpendPrivateKeyP *icoin_priv; - struct TALER_CoinSpendPublicKeyP icoin_pub; - struct GNUNET_HashCode h_icoin_pub; - struct TALER_Amount *iamount; - const struct TALER_TESTING_Command *icoin_cmd; - - if (NULL == - (icoin_cmd = - TALER_TESTING_interpreter_lookup_command (rls->is, - icoin_reference)) ) + const struct TALER_TESTING_Command *pay_cmd; + + if (NULL == (pay_cmd = TALER_TESTING_interpreter_lookup_command ( + rls->is, + rls->pay_reference))) { - GNUNET_break (0); - TALER_LOG_ERROR ("Bad reference `%s'\n", - icoin_reference); - TALER_TESTING_interpreter_fail (rls->is); GNUNET_CONTAINER_multihashmap_destroy (map); - return; + TALER_TESTING_FAIL (rls->is); } if (GNUNET_OK != - TALER_TESTING_get_trait_coin_priv (icoin_cmd, - 0, - &icoin_priv)) + TALER_TESTING_get_trait_coin_reference ( + pay_cmd, + 0, + &coin_reference)) { - GNUNET_break (0); - TALER_LOG_ERROR ("Command `%s' failed to give coin priv trait\n", - icoin_reference); - TALER_TESTING_interpreter_fail (rls->is); GNUNET_CONTAINER_multihashmap_destroy (map); - return; + TALER_TESTING_FAIL (rls->is); } - GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv, - &icoin_pub.eddsa_pub); - GNUNET_CRYPTO_hash (&icoin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP), - &h_icoin_pub); - - iamount = GNUNET_CONTAINER_multihashmap_get (map, - &h_icoin_pub); - - /* Can be NULL: not all coins are involved in refund */ - if (NULL == iamount) - continue; - GNUNET_assert (0 <= - TALER_amount_add (&acc, - &acc, - iamount)); } - GNUNET_free (coin_reference_dup); - - if (NULL != - (increase_cmd - = TALER_TESTING_interpreter_lookup_command (rls->is, - rls->increase_reference))) + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero ("EUR", + &acc)); { - if (GNUNET_OK != - TALER_TESTING_get_trait_string (increase_cmd, - 0, - &refund_amount)) - TALER_TESTING_FAIL (rls->is); + char *coin_reference_dup; - if (GNUNET_OK != - TALER_string_to_amount (refund_amount, - &ra)) - TALER_TESTING_FAIL (rls->is); + coin_reference_dup = GNUNET_strdup (coin_reference); + for (icoin_reference = strtok (coin_reference_dup, ";"); + NULL != icoin_reference; + icoin_reference = strtok (NULL, ";")) + { + const struct TALER_CoinSpendPrivateKeyP *icoin_priv; + struct TALER_CoinSpendPublicKeyP icoin_pub; + struct GNUNET_HashCode h_icoin_pub; + const struct TALER_MERCHANT_RefundDetail *idetail; + const struct TALER_TESTING_Command *icoin_cmd; + + if (NULL == + (icoin_cmd = + TALER_TESTING_interpreter_lookup_command (rls->is, + icoin_reference)) ) + { + GNUNET_break (0); + TALER_LOG_ERROR ("Bad reference `%s'\n", + icoin_reference); + TALER_TESTING_interpreter_fail (rls->is); + GNUNET_CONTAINER_multihashmap_destroy (map); + return; + } + + if (GNUNET_OK != + TALER_TESTING_get_trait_coin_priv (icoin_cmd, + 0, + &icoin_priv)) + { + GNUNET_break (0); + TALER_LOG_ERROR ("Command `%s' failed to give coin priv trait\n", + icoin_reference); + TALER_TESTING_interpreter_fail (rls->is); + GNUNET_CONTAINER_multihashmap_destroy (map); + return; + } + GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv, + &icoin_pub.eddsa_pub); + TALER_LOG_DEBUG ("Looking at coin %s\n", + TALER_B2S (&icoin_pub)); + GNUNET_CRYPTO_hash (&icoin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + &h_icoin_pub); + + idetail = GNUNET_CONTAINER_multihashmap_get (map, + &h_icoin_pub); + + /* Can be NULL: not all coins are involved in refund */ + if (NULL == idetail) + continue; + TALER_LOG_DEBUG ("Found coin %s refund of %s\n", + TALER_B2S (&idetail->coin_pub), + TALER_amount2s (&idetail->refund_amount)); + GNUNET_assert (0 <= + TALER_amount_add (&acc, + &acc, + &idetail->refund_amount)); + } + GNUNET_free (coin_reference_dup); } - else + + { - GNUNET_assert (NULL != rls->refund_amount); + const struct TALER_TESTING_Command *increase_cmd; - if (GNUNET_OK != - TALER_string_to_amount (rls->refund_amount, - &ra)) - TALER_TESTING_FAIL (rls->is); - } + if (NULL != + (increase_cmd + = TALER_TESTING_interpreter_lookup_command (rls->is, + rls->increase_reference))) + { + if (GNUNET_OK != + TALER_TESTING_get_trait_string (increase_cmd, + 0, + &refund_amount)) + TALER_TESTING_FAIL (rls->is); + + if (GNUNET_OK != + TALER_string_to_amount (refund_amount, + &ra)) + TALER_TESTING_FAIL (rls->is); + } + else + { + GNUNET_assert (NULL != rls->refund_amount); - GNUNET_CONTAINER_multihashmap_iterate (map, - &hashmap_free, - NULL); + if (GNUNET_OK != + TALER_string_to_amount (rls->refund_amount, + &ra)) + TALER_TESTING_FAIL (rls->is); + } + } GNUNET_CONTAINER_multihashmap_destroy (map); /* Check that what the backend claims to have been refunded @@ -304,10 +314,14 @@ refund_lookup_cb (void *cls, if (0 != TALER_amount_cmp (&acc, &ra)) { + char *a1; + + a1 = TALER_amount_to_string (&ra); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Incomplete refund: expected '%s', got '%s'\n", - TALER_amount_to_string (&ra), - TALER_amount_to_string (&acc)); + a1, + TALER_amount2s (&acc)); + GNUNET_free (a1); TALER_TESTING_interpreter_fail (rls->is); return; } diff --git a/src/lib/testing_api_cmd_track_transaction.c b/src/lib/testing_api_cmd_track_transaction.c @@ -229,12 +229,15 @@ track_transaction_traits (void *cls, struct TrackTransactionState *tts = cls; struct TALER_WireTransferIdentifierRawP *wtid_ptr; - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - tts->wtid_str, - strlen (tts->wtid_str), - &tts->wtid, - sizeof (struct TALER_WireTransferIdentifierRawP))) + if (MHD_HTTP_OK != tts->http_status) + return GNUNET_SYSERR; + if ( (NULL != tts->wtid_str) && + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (tts->wtid_str, + strlen (tts->wtid_str), + &tts->wtid, + sizeof (struct + TALER_WireTransferIdentifierRawP))) ) wtid_ptr = NULL; else wtid_ptr = &tts->wtid;