merchant

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

commit f5c484e625cde6b6822ea75c20cecf8f4db7c54d
parent ce97f3d2e12b4da31eb0f611d401f56ce8052de5
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 30 Apr 2020 00:25:17 +0200

sql-ing

Diffstat:
Msrc/backend/taler-merchant-httpd_exchanges.c | 3+++
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 3+--
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.h | 2+-
Msrc/backenddb/merchant-0001.sql | 6++++--
Msrc/backenddb/plugin_merchantdb_postgres.c | 585++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/include/taler_merchant_service.h | 10+++++-----
Msrc/include/taler_merchant_testing_lib.h | 8++++----
Msrc/include/taler_merchantdb_plugin.h | 31++++++++++++++++++++++++++++---
Msrc/lib/Makefile.am | 1-
Dsrc/lib/merchant_api_pay.c | 1067-------------------------------------------------------------------------------
Asrc/lib/merchant_api_post_order_pay.c | 1067+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/Makefile.am | 3---
Msrc/testing/testing_api_trait_refund_entry.c | 14++++++++------
13 files changed, 1585 insertions(+), 1215 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c @@ -830,6 +830,9 @@ keys_mgmt_cb (void *cls, exchange->url); } } + // FIXME: go over keys->sign_keys and STORE + // all of those into our database! + expire = TALER_EXCHANGE_check_keys_current (exchange->conn, GNUNET_NO, GNUNET_NO); diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -641,8 +641,6 @@ deposit_cb (void *cls, pc->hc->infix, GNUNET_h2s (&pc->h_contract_terms), pc->hc->instance->settings.id); - /* NOTE: not run in any transaction block, simply as a - transaction by itself! */ TMH_db->preflight (TMH_db->cls); qs = TMH_db->insert_deposit (TMH_db->cls, pc->hc->instance->settings.id, @@ -653,6 +651,7 @@ deposit_cb (void *cls, &dc->deposit_fee, &dc->refund_fee, &dc->wire_fee, + &pc->wm->h_wire, exchange_sig, exchange_pub); if (0 > qs) diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.h b/src/backend/taler-merchant-httpd_post-orders-ID-pay.h @@ -34,7 +34,7 @@ MH_force_pc_resume (void); /** - * Process payment for an order. + * Process payment for a claimed order. * * @param rh context of the handler * @param connection the MHD connection to handle diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql @@ -61,7 +61,7 @@ COMMENT ON COLUMN merchant_exchange_signing_keys.master_pub CREATE TABLE IF NOT EXISTS merchant_instances (merchant_serial BIGSERIAL PRIMARY KEY ,merchant_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(merchant_pub)=32) - ,merchant_id VARCHAR NOT NULL + ,merchant_id VARCHAR NOT NULL UNIQUE ,merchant_name VARCHAR NOT NULL ,address BYTEA NOT NULL ,jurisdiction BYTEA NOT NULL @@ -279,6 +279,7 @@ CREATE TABLE IF NOT EXISTS merchant_deposits (deposit_serial BIGSERIAL PRIMARY KEY ,order_serial BIGINT REFERENCES merchant_contract_terms (order_serial) ON DELETE CASCADE + ,deposit_timestamp INT8 NOT NULL ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) ,exchange_url VARCHAR NOT NULL ,amount_with_fee_val INT8 NOT NULL @@ -292,7 +293,6 @@ CREATE TABLE IF NOT EXISTS merchant_deposits ,signkey_serial BIGINT NOT NULL REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE CASCADE ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64) - ,exchange_timestamp INT8 NOT NULL ,account_serial BIGINT NOT NULL REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE ,UNIQUE (order_serial, coin_pub) @@ -301,6 +301,8 @@ COMMENT ON TABLE merchant_deposits IS 'Table with the deposit confirmations for each coin we deposited at the exchange'; COMMENT ON COLUMN merchant_deposits.signkey_serial IS 'Online signing key of the exchange on the deposit confirmation'; +COMMENT ON COLUMN merchant_deposits.deposit_timestamp + IS 'Time when we received the deposit confirmation from the exchange (not rounded)'; COMMENT ON COLUMN merchant_deposits.exchange_sig IS 'Signature of the exchange over the deposit confirmation'; COMMENT ON COLUMN merchant_deposits.wire_fee_val diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -1501,6 +1501,382 @@ postgres_delete_contract_terms (void *cls, } +/** + * Closure for #lookup_deposits_cb(). + */ +struct LookupDepositsContext +{ + /** + * Function to call with results. + */ + TALER_MERCHANTDB_DepositsCallback cb; + + /** + * Closure for @e cls. + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Transaction status (set). + */ + enum GNUNET_DB_QueryStatus qs; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param[in,out] cls of type `struct LookupDepositsContext *` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +lookup_deposits_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupDepositsContext *ldc = cls; + struct PostgresClosure *pg = ldc->pg; + + for (unsigned int i = 0; i<num_results; i++) + { + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + struct TALER_Amount refund_fee; + struct TALER_Amount wire_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 ("deposit_fee", + &deposit_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee", + &refund_fee), + TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", + &wire_fee), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + ldc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + ldc->qs = i + 1; + ldc->cb (ldc->cb_cls, + &coin_pub, + &amount_with_fee, + &deposit_fee, + &refund_fee, + &wire_fee); + GNUNET_PQ_cleanup_result (rs); /* technically useless here */ + } +} + + +/** + * Lookup information about coins that were successfully deposited for a + * given contract. + * + * @param cls closure + * @param instance_id instance to lookup deposits for + * @param h_contract_terms proposal data's hashcode + * @param cb function to call with payment data + * @param cb_cls closure for @a cb + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_lookup_deposits (void *cls, + const char *instance_id, + const struct GNUNET_HashCode *h_contract_terms, + TALER_MERCHANTDB_DepositsCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_end + }; + struct LookupDepositsContext ldc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg + }; + enum GNUNET_DB_QueryStatus qs; + + /* no preflight check here, run in its own transaction by the caller! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finding deposits for h_contract_terms '%s'\n", + GNUNET_h2s (h_contract_terms)); + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_deposits", + params, + &lookup_deposits_cb, + &ldc); + if (qs <= 0) + return qs; + return ldc.qs; +} + + +/** + * Insert an exchange signing key into our database. + * + * @param cls closure + * @param master_pub exchange master public key used for @a master_sig + * @param exchange_pub exchange signing key to insert + * @param start_date when does the signing key become valid + * @param expire_date when does the signing key stop being used + * @param end_date when does the signing key become void as proof + * @param master_sig signature of @a master_pub over the @a exchange_pub and the dates + */ +static enum GNUNET_DB_QueryStatus +postgres_insert_exchange_signkey ( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute expire_date, + struct GNUNET_TIME_Absolute end_date, + const struct TALER_MasterSignatureP *master_sig) +{ + // FIXME! +} + + +/** + * Insert payment confirmation from the exchange into the database. + * + * @param cls closure + * @param instance_id instance to lookup deposits for + * @param h_contract_terms proposal data's hashcode + * @param coin_pub public key of the coin + * @param exchange_url URL of the exchange that issued @a coin_pub + * @param amount_with_fee amount the exchange will deposit for this coin + * @param deposit_fee fee the exchange will charge for this coin + * @param wire_fee wire fee the exchange charges + * @param h_wire hash of the wire details of the target account of the merchant + * @param exchange_timestamp timestamp from the exchange + * @param exchange_sig signature from exchange that coin was accepted + * @param exchange_pub signgin key that was used for @a exchange_sig + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_insert_deposit (void *cls, + const char *instance_id, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const char *exchange_url, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *deposit_fee, + const struct TALER_Amount *refund_fee, + const struct TALER_Amount *wire_fee, + const struct GNUNET_HashCode *h_wire, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_ExchangePublicKeyP *exchange_pub) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_absolute_time (&now), /* $3 */ + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_string (exchange_url), + TALER_PQ_query_param_amount (amount_with_fee), /* $6/$7 */ + TALER_PQ_query_param_amount (deposit_fee), /* $8, $9 */ + TALER_PQ_query_param_amount (refund_fee), /* $10, $11 */ + TALER_PQ_query_param_amount (wire_fee), /* $12, $13 */ + GNUNET_PQ_query_param_auto_from_type (h_wire), /* $14 */ + GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $15 */ + GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $16 */ + GNUNET_PQ_query_param_end + }; + + /* no preflight check here, run in transaction by caller! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Storing deposit for instance `%s' h_contract_terms `%s', coin_pub: `%s', amount_with_fee: %s\n", + instance_id, + GNUNET_h2s (h_contract_terms), + TALER_B2S (coin_pub), + TALER_amount2s (amount_with_fee)); + check_connection (pg); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "insert_deposit", + params); + +} + + +/** + * Closure for #lookup_refunds_cb(). + */ +struct LookupRefundsContext +{ + /** + * Function to call for each refund. + */ + TALER_MERCHANTDB_RefundCallback rc; + + /** + * Closure for @e rc. + */ + void *rc_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Transaction result. + */ + enum GNUNET_DB_QueryStatus qs; +}; + + +/** + * Function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls of type `struct LookupRefundsContext *` + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +lookup_refunds_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupRefundsContext *lrc = cls; + struct PostgresClosure *pg = lrc->pg; + + for (unsigned int i = 0; i<num_results; i++) + { + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount refund_amount; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("coin_pub", + &coin_pub), + TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount", + &refund_amount), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + lrc->qs = GNUNET_DB_STATUS_HARD_ERROR; + return; + } + lrc->qs = i + 1; + lrc->rc (lrc->rc_cls, + &coin_pub, + &refund_amount); + GNUNET_PQ_cleanup_result (rs); /* technically useless here */ + } +} + + +/** + * Obtain refunds associated with a contract. + * + * @param cls closure, typically a connection to the db + * @param instance_id instance to lookup refunds for + * @param h_contract_terms hash code of the contract + * @param rc function to call for each coin on which there is a refund + * @param rc_cls closure for @a rc + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_lookup_refunds (void *cls, + const char *instance_id, + const struct GNUNET_HashCode *h_contract_terms, + TALER_MERCHANTDB_RefundCallback rc, + void *rc_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_end + }; + struct LookupRefundsContext lrc = { + .rc = rc, + .rc_cls = rc_cls, + .pg = pg + }; + enum GNUNET_DB_QueryStatus qs; + + /* no preflight check here, run in transaction by caller! */ + TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n", + GNUNET_h2s (h_contract_terms), + instance_id); + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_refunds", + params, + &lookup_refunds_cb, + &lrc); + if (0 >= qs) + return qs; + return lrc.qs; +} + + +/** + * Mark contract as paid and store the current @a session_id + * for which the contract was paid. + * + * @param cls closure + * @param instance_id instance to mark contract as paid for + * @param h_contract_terms hash of the contract that is now paid + * @param session_id the session that paid the contract, can be NULL + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_mark_contract_paid (void *cls, + const char *instance_id, + const struct GNUNET_HashCode *h_contract_terms, + const char *session_id) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_auto_from_type (h_contract_terms), + GNUNET_PQ_query_param_string (session_id), + GNUNET_PQ_query_param_end + }; + + /* no preflight check here, run in transaction by caller! */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Marking h_contract_terms '%s' of %s as paid for session `%s'\n", + GNUNET_h2s (h_contract_terms), + instance_id, + session_id); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "mark_contract_paid", + params); +} + + /* ********************* OLD API ************************** */ /** @@ -1580,44 +1956,6 @@ postgres_find_paid_contract_terms_from_hash (void *cls, /** - * Mark contract terms as paid. Needed by /history as only paid - * contracts must be shown. - * - * NOTE: we can't get the list of (paid) contracts from the - * transactions table because it lacks contract_terms plain JSON. In - * facts, the protocol doesn't allow to store contract_terms in - * transactions table, as /pay handler doesn't receive this data (only - * /proposal does). - * - * @param cls closure - * @param h_contract_terms hash of the contract that is now paid - * @param merchant_pub merchant's public key - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_mark_proposal_paid (void *cls, - const struct GNUNET_HashCode *h_contract_terms, - const struct - TALER_MerchantPublicKeyP *merchant_pub) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_end - }; - - TALER_LOG_DEBUG ("Marking proposal paid, h_contract_terms: '%s'," - " merchant_pub: '%s'\n", - GNUNET_h2s (h_contract_terms), - TALER_B2S (merchant_pub)); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "mark_proposal_paid", - params); -} - - -/** * Store the order ID that was used to pay for a resource within a session. * * @param cls closure @@ -1696,64 +2034,6 @@ postgres_find_session_info (void *cls, /** - * Insert payment confirmation from the exchange into the database. - * - * @param cls closure - * @param order_id identificator of the proposal associated with this revenue - * @param merchant_pub merchant's public key - * @param coin_pub public key of the coin - * @param amount_with_fee amount the exchange will deposit for this coin - * @param deposit_fee fee the exchange will charge for this coin - * @param refund_fee fee the exchange will charge for refunding this coin - * @param wire_fee wire fee changed by the exchange - * @param signkey_pub public key used by the exchange for @a exchange_proof - * @param exchange_proof proof from exchange that coin was accepted - * @return transaction status - */ -static enum GNUNET_DB_QueryStatus -postgres_store_deposit (void *cls, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const char *exchange_url, - const struct TALER_Amount *amount_with_fee, - const struct TALER_Amount *deposit_fee, - const struct TALER_Amount *refund_fee, - const struct TALER_Amount *wire_fee, - const struct TALER_ExchangePublicKeyP *signkey_pub, - const json_t *exchange_proof) -{ - struct PostgresClosure *pg = cls; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (h_contract_terms), - GNUNET_PQ_query_param_auto_from_type (merchant_pub), - GNUNET_PQ_query_param_auto_from_type (coin_pub), - GNUNET_PQ_query_param_string (exchange_url), - TALER_PQ_query_param_amount (amount_with_fee), - TALER_PQ_query_param_amount (deposit_fee), - TALER_PQ_query_param_amount (refund_fee), - TALER_PQ_query_param_amount (wire_fee), - GNUNET_PQ_query_param_auto_from_type (signkey_pub), - TALER_PQ_query_param_json (exchange_proof), - GNUNET_PQ_query_param_end - }; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Storing payment for h_contract_terms `%s', coin_pub: `%s', amount_with_fee: %s\n", - GNUNET_h2s (h_contract_terms), - TALER_B2S (coin_pub), - TALER_amount2s (amount_with_fee)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Merchant pub is `%s'\n", - TALER_B2S (merchant_pub)); - check_connection (pg); - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_deposit", - params); -} - - -/** * Insert mapping of @a coin_pub and @a h_contract_terms to * corresponding @a wtid. * @@ -2075,7 +2355,7 @@ find_payments_cb (void *cls, * @param cb_cls closure for @a cb * @return transaction status */ -static enum GNUNET_DB_QueryStatus +enum GNUNET_DB_QueryStatus postgres_find_payments (void *cls, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub, @@ -2535,7 +2815,7 @@ struct GetRefundsContext /** * Function to call for each refund. */ - TALER_MERCHANTDB_RefundCallback rc; + TALER_MERCHANTDB_CoinRefundCallback rc; /** * Closure for @e rc. @@ -2626,12 +2906,12 @@ get_refunds_cb (void *cls, * @param rc_cls closure for @a rc * @return transaction status */ -static enum GNUNET_DB_QueryStatus +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, + TALER_MERCHANTDB_CoinRefundCallback rc, void *rc_cls) { struct PostgresClosure *pg = cls; @@ -5002,14 +5282,45 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) " (NOT paid) ) OR" " (creation_time + $3 > $4) )", 4), - - /* OLD API: */ - -#if 0 + GNUNET_PQ_make_prepare ("lookup_deposits", + "SELECT" + " coin_pub" + ",amount_with_fee_val" + ",amount_with_fee_frac" + ",deposit_fee_val" + ",deposit_fee_frac" + ",refund_fee_val" + ",refund_fee_frac" + ",wire_fee_val" + ",wire_fee_frac" + " FROM merchant_deposits" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))", + 2), GNUNET_PQ_make_prepare ("insert_deposit", + "WITH md AS" + " (SELECT account_serial, merchant_serial" + " FROM merchant_accounts" + " WHERE h_wire=$14" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))" + ", ed AS" + " (SELECT signkey_serial" + " FROM merchant_exchange_signing_keys" + " WHERE exchange_pub=$16" + " ORDER BY start_date DESC" + " LIMIT 1)" "INSERT INTO merchant_deposits" - "(h_contract_terms" - ",merchant_pub" + "(order_serial" + ",deposit_timestamp" ",coin_pub" ",exchange_url" ",amount_with_fee_val" @@ -5020,10 +5331,47 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) ",refund_fee_frac" ",wire_fee_val" ",wire_fee_frac" - ",signkey_pub" - ",exchange_proof) VALUES " - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)", - 14), + ",exchange_sig" + ",signkey_serial" + ",account_serial)" + " SELECT " + " order_serial" + " ,$3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $15" + " ,ed.signkey_serial" + " ,md.account_serial" + " FROM merchant_contract_terms" + " JOIN md USING (merchant_serial)" + " FULL OUTER JOIN ed ON TRUE" + " WHERE h_contract_terms=$2", + 16), + GNUNET_PQ_make_prepare ("lookup_refunds", + "SELECT" + " coin_pub" + ",refund_amount_val" + ",refund_amount_frac" + " FROM merchant_refunds" + " WHERE order_serial=" + " (SELECT order_serial" + " FROM merchant_contract_terms" + " WHERE h_contract_terms=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1))", + 2), + GNUNET_PQ_make_prepare ("mark_contract_paid", + "UPDATE merchant_contract_terms SET" + " paid=TRUE" + ",session_id=$3" + " WHERE h_contract_terms=$2" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)", + 3), + /* OLD API: */ + +#if 0 GNUNET_PQ_make_prepare ("insert_transfer", "INSERT INTO merchant_transfers" "(h_contract_terms" @@ -5071,12 +5419,6 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) " VALUES " "($1, $2, $3, $4, $5)", 5), - GNUNET_PQ_make_prepare ("mark_proposal_paid", - "UPDATE merchant_contract_terms SET" - " paid=TRUE" - " WHERE h_contract_terms=$1" - " AND merchant_pub=$2", - 2), GNUNET_PQ_make_prepare ("insert_wire_fee", "INSERT INTO exchange_wire_fees" "(exchange_pub" @@ -5418,30 +5760,31 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->lookup_contract_terms = &postgres_lookup_contract_terms; plugin->insert_contract_terms = &postgres_insert_contract_terms; plugin->delete_contract_terms = &postgres_delete_contract_terms; + plugin->lookup_deposits = &postgres_lookup_deposits; + plugin->insert_exchange_signkey = &postgres_insert_exchange_signkey; + plugin->insert_deposit = &postgres_insert_deposit; + plugin->lookup_refunds = &postgres_lookup_refunds; + plugin->mark_contract_paid = &postgres_mark_contract_paid; + /* OLD API: */ plugin->find_contract_terms_from_hash = &postgres_find_contract_terms_from_hash; plugin->find_paid_contract_terms_from_hash = &postgres_find_paid_contract_terms_from_hash; - plugin->store_deposit = &postgres_store_deposit; plugin->store_coin_to_transfer = &postgres_store_coin_to_transfer; plugin->store_transfer_to_proof = &postgres_store_transfer_to_proof; plugin->store_wire_fee_by_exchange = &postgres_store_wire_fee_by_exchange; plugin->find_payments_by_hash_and_coin = &postgres_find_payments_by_hash_and_coin; - plugin->find_payments = &postgres_find_payments; plugin->find_transfers_by_hash = &postgres_find_transfers_by_hash; plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid; plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid; plugin->get_authorized_tip_amount = &postgres_get_authorized_tip_amount; - plugin->get_refunds_from_contract_terms_hash = - &postgres_get_refunds_from_contract_terms_hash; plugin->lookup_wire_fee = &postgres_lookup_wire_fee; plugin->increase_refund_for_contract_NT = &postgres_increase_refund_for_contract_NT; plugin->get_refund_proof = &postgres_get_refund_proof; plugin->put_refund_proof = &postgres_put_refund_proof; - plugin->mark_proposal_paid = &postgres_mark_proposal_paid; plugin->insert_session_info = &postgres_insert_session_info; plugin->find_session_info = &postgres_find_session_info; plugin->enable_tip_reserve_TR = &postgres_enable_tip_reserve_TR; diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -1524,7 +1524,7 @@ TALER_MERCHANT_order_pay_frontend ( const char *order_id, unsigned int num_coins, const struct TALER_MERCHANT_PaidCoin coins[], - TALER_MERCHANT_PayCallback pay_cb, + TALER_MERCHANT_OrderPayCallback pay_cb, void *pay_cb_cls); @@ -1617,7 +1617,7 @@ TALER_MERCHANT_order_pay ( const char *order_id, unsigned int num_coins, const struct TALER_MERCHANT_PayCoin coins[], - TALER_MERCHANT_PayCallback pay_cb, + TALER_MERCHANT_OrderPayCallback pay_cb, void *pay_cb_cls); @@ -1638,7 +1638,7 @@ TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph); /** * Handle for an POST /orders/$ID/abort operation. */ -struct TALER_MERCHANT_AbortHandle; +struct TALER_MERCHANT_OrderAbortHandle; /** @@ -1710,7 +1710,7 @@ typedef void * @param payref_cb_cls closure for @a pay_cb * @return a handle for this request */ -struct TALER_MERCHANT_AbortHandle * +struct TALER_MERCHANT_OrderAbortHandle * TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx, const char *merchant_url, const struct GNUNET_HashCode *h_contract, @@ -1737,7 +1737,7 @@ TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx, * @param oah the abort request handle */ void -TALER_MERCHANT_order_abort_cancel (struct TALER_MERCHANT_OrderAbortandle *oah); +TALER_MERCHANT_order_abort_cancel (struct TALER_MERCHANT_OrderAbortHandle *oah); /* ********************* OLD ************************** */ diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -969,9 +969,9 @@ TALER_TESTING_get_trait_h_contract_terms ( * @return the trait */ struct TALER_TESTING_Trait -TALER_TESTING_make_trait_refund_entry ( +TALER_TESTING_make_trait_refund_entry ( // FIXME: rename: entry->detail unsigned int index, - const struct TALER_MERCHANT_RefundEntry *refund_entry); + const struct TALER_MERCHANT_RefundDetail *refund_entry); /** @@ -983,10 +983,10 @@ TALER_TESTING_make_trait_refund_entry ( * @return #GNUNET_OK on success */ int -TALER_TESTING_get_trait_refund_entry ( +TALER_TESTING_get_trait_refund_entry ( // FIXME: rename: entry->detail const struct TALER_TESTING_Command *cmd, unsigned int index, - const struct TALER_MERCHANT_RefundEntry **refund_entry); + const struct TALER_MERCHANT_RefundDetail **refund_entry); /** * Create a /tip-authorize CMD, specifying the Taler error code diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -311,7 +311,7 @@ typedef void * @param wire_fee wire fee the exchange charges */ typedef void -(*TALER_MERCHANTDB_DepositCallback)( +(*TALER_MERCHANTDB_DepositsCallback)( void *cls, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_Amount *amount_with_fee, @@ -904,9 +904,32 @@ struct TALER_MERCHANTDB_Plugin (*lookup_deposits)(void *cls, const char *instance_id, const struct GNUNET_HashCode *h_contract_terms, - TALER_MERCHANTDB_DepositCallback cb, + TALER_MERCHANTDB_DepositsCallback cb, void *cb_cls); + + /** + * Insert an exchange signing key into our database. + * + * @param cls closure + * @param master_pub exchange master public key used for @a master_sig + * @param exchange_pub exchange signing key to insert + * @param start_date when does the signing key become valid + * @param expire_date when does the signing key stop being used + * @param end_date when does the signing key become void as proof + * @param master_sig signature of @a master_pub over the @a exchange_pub and the dates + */ + enum GNUNET_DB_QueryStatus + (*insert_exchange_signkey)( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Absolute expire_date, + struct GNUNET_TIME_Absolute end_date, + const struct TALER_MasterSignatureP *master_sig); + + /** * Insert payment confirmation from the exchange into the database. * @@ -918,8 +941,9 @@ struct TALER_MERCHANTDB_Plugin * @param amount_with_fee amount the exchange will deposit for this coin * @param deposit_fee fee the exchange will charge for this coin * @param wire_fee wire fee the exchange charges - * @param exchange_pub public key used by the exchange for @a exchange_sig + * @param h_wire hash of the wire details of the target account of the merchant * @param exchange_sig signature from exchange that coin was accepted + * @param exchange_pub signgin key that was used for @a exchange_sig * @return transaction status */ enum GNUNET_DB_QueryStatus @@ -932,6 +956,7 @@ struct TALER_MERCHANTDB_Plugin const struct TALER_Amount *deposit_fee, const struct TALER_Amount *refund_fee, const struct TALER_Amount *wire_fee, + const struct GNUNET_HashCode *h_wire, const struct TALER_ExchangeSignatureP *exchange_sig, const struct TALER_ExchangePublicKeyP *exchange_pub); diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -33,7 +33,6 @@ libtalermerchant_la_SOURCES = \ \ merchant_api_check_payment.c \ merchant_api_history.c \ - merchant_api_pay.c \ merchant_api_poll_payment.c \ merchant_api_refund.c \ merchant_api_refund_increase.c \ diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c @@ -1,1067 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015, 2016, 2017, 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 Foundation; either version 2.1, - or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with TALER; see the file COPYING.LGPL. - If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/merchant_api_pay.c - * @brief Implementation of the /pay request - * of the merchant's HTTP API - * @author Christian Grothoff - * @author Marcello Stanisci - */ -#include "platform.h" -#include <curl/curl.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_merchant_service.h" -#include <taler/taler_json_lib.h> -#include <taler/taler_signatures.h> -#include <taler/taler_exchange_service.h> -#include <taler/taler_curl_lib.h> - - -/** - * @brief A Pay Handle - */ -struct TALER_MERCHANT_Pay -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result in "pay" @e mode. - */ - TALER_MERCHANT_PayCallback pay_cb; - - /** - * Closure for @a pay_cb. - */ - void *pay_cb_cls; - - /** - * Function to call with the result in "abort-refund" @e mode. - */ - TALER_MERCHANT_PayRefundCallback abort_cb; - - /** - * Closure for @a abort_cb. - */ - void *abort_cb_cls; - - /** - * Operational mode, either "pay" or "abort-refund". - */ - const char *mode; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * The coins we are paying with. - */ - struct TALER_MERCHANT_PaidCoin *coins; - - /** - * Number of @e coins we are paying with. - */ - unsigned int num_coins; - - /** - * Hash of the contract, only available in "abort-refund" mode. - */ - struct GNUNET_HashCode h_contract_terms; - -}; - - -/** - * Check that the response for a /pay refund is well-formed, - * and call the application callback with the result if it is - * OK. Otherwise returns #GNUNET_SYSERR. - * - * @param ph handle to operation that created the reply - * @param json the reply to parse - * @return #GNUNET_OK on success - */ -static int -check_abort_refund (struct TALER_MERCHANT_Pay *ph, - const json_t *json) -{ - json_t *refunds; - unsigned int num_refunds; - struct TALER_MerchantPublicKeyP merchant_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("refund_permissions", &refunds), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - num_refunds = json_array_size (refunds); - { - struct TALER_MERCHANT_RefundEntry res[GNUNET_NZL (num_refunds)]; - - for (unsigned int i = 0; i<num_refunds; i++) - { - struct TALER_MerchantSignatureP *sig = &res[i].merchant_sig; - json_t *refund = json_array_get (refunds, i); - struct GNUNET_JSON_Specification spec_detail[] = { - GNUNET_JSON_spec_fixed_auto ("merchant_sig", - sig), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &res[i].coin_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &res[i].rtransaction_id), - GNUNET_JSON_spec_end () - }; - int found; - - if (GNUNET_OK != - GNUNET_JSON_parse (refund, - spec_detail, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - - found = -1; - for (unsigned int j = 0; j<ph->num_coins; j++) - { - if (0 == memcmp (&ph->coins[j].coin_pub, - &res[i].coin_pub, - sizeof - (struct TALER_CoinSpendPublicKeyP))) - { - found = j; - break; - } - } - if (-1 == found) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - - { - struct TALER_RefundRequestPS rr = { - .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND), - .purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)), - .h_contract_terms = ph->h_contract_terms, - .coin_pub = res[i].coin_pub, - .merchant = merchant_pub, - .rtransaction_id = GNUNET_htonll (res[i].rtransaction_id) - }; - - TALER_amount_hton (&rr.refund_amount, - &ph->coins[found].amount_with_fee); - TALER_amount_hton (&rr.refund_fee, - &ph->coins[found].refund_fee); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, - &rr, - &sig->eddsa_sig, - &merchant_pub.eddsa_pub)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - } - } - { - struct TALER_MERCHANT_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; - - ph->abort_cb (ph->abort_cb_cls, - &hr, - &merchant_pub, - &ph->h_contract_terms, - num_refunds, - res); - } - ph->abort_cb = NULL; - } - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * We got a 403 response back from the exchange (or the merchant). - * Now we need to check the provided cryptographic proof that the - * coin was actually already spent! - * - * @param pc handle of the original coin we paid with - * @param json cryptographic proof of coin's transaction - * history as was returned by the exchange/merchant - * @return #GNUNET_OK if proof checks out - */ -static int -check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc, - json_t *json) -{ - struct TALER_Amount spent; - struct TALER_Amount spent_plus_contrib; - - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */ - pc->amount_with_fee.currency, - &pc->coin_pub, - json, - &spent)) - { - /* Exchange's history fails to verify */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&spent_plus_contrib, - &spent, - &pc->amount_with_fee)) - { - /* We got an integer overflow? Bad application! */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (-1 != TALER_amount_cmp (&pc->denom_value, - &spent_plus_contrib)) - { - /* according to our calculations, the transaction should - have still worked, exchange error! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Accepting proof of double-spending\n"); - return GNUNET_OK; -} - - -/** - * We got a 409 response back from the exchange (or the merchant). - * Now we need to check the provided cryptographic proof that the - * coin was actually already spent! - * - * @param ph handle of the original pay operation - * @param json cryptographic proof returned by the - * exchange/merchant - * @return #GNUNET_OK if proof checks out - */ -static int -check_conflict (struct TALER_MERCHANT_Pay *ph, - const json_t *json) -{ - json_t *history; - json_t *ereply; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("exchange_reply", &ereply), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), - GNUNET_JSON_spec_end () - }; - struct GNUNET_JSON_Specification hspec[] = { - GNUNET_JSON_spec_json ("history", &history), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_JSON_parse (ereply, - hspec, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (spec); - - for (unsigned int i = 0; i<ph->num_coins; i++) - { - if (0 == memcmp (&ph->coins[i].coin_pub, - &coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP))) - { - int ret; - - ret = check_coin_history (&ph->coins[i], - history); - GNUNET_JSON_parse_free (hspec); - return ret; - } - } - GNUNET_break_op (0); /* complaint is not about any of the coins - that we actually paid with... */ - GNUNET_JSON_parse_free (hspec); - return GNUNET_SYSERR; -} - - -/** - * Function called when we're done processing the - * HTTP /pay request. - * - * @param cls the `struct TALER_MERCHANT_Pay` - * @param response_code HTTP response code, 0 on error - * @param json response body, NULL if not in JSON - */ -static void -handle_pay_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_MERCHANT_Pay *ph = cls; - const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json - }; - - ph->job = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "/pay completed with response code %u\n", - (unsigned int) response_code); - if (0 == strcasecmp (ph->mode, - "pay")) - { - switch (response_code) - { - case 0: - hr.ec = TALER_EC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - break; - /* Tolerating Not Acceptable because sometimes - * - especially in tests - we might want to POST - * coins one at a time. */ - case MHD_HTTP_NOT_ACCEPTABLE: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_BAD_REQUEST: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* This should never happen, either us - * or the merchant is buggy (or API version conflict); - * just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says we tried to abort the payment - * after it was successful. We should pass the JSON reply to the - * application */ - break; - case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the - application */ - break; - case MHD_HTTP_PRECONDITION_FAILED: - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - /* Nothing really to verify, the merchant is blaming us for failing to - satisfy some constraint (likely it does not like our exchange because - of some disagreement on the PKI). We should pass the JSON reply to the - application */ - break; - case MHD_HTTP_REQUEST_TIMEOUT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* The merchant couldn't generate a timely response, likely because - it itself waited too long on the exchange. - Pass on to application. */ - break; - case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - if (GNUNET_OK != check_conflict (ph, - json)) - { - GNUNET_break_op (0); - response_code = 0; - } - break; - case MHD_HTTP_GONE: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* The merchant says we are too late, the offer has expired or some - denomination key of a coin involved has expired. - Might be a disagreement in timestamps? Still, pass on to application. */ - break; - case MHD_HTTP_FAILED_DEPENDENCY: - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - /* Nothing really to verify, the merchant is blaming the exchange. - We should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Server had an internal issue; we should retry, - but this API leaves this to the application */ - break; - case MHD_HTTP_SERVICE_UNAVAILABLE: - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - /* Exchange couldn't respond properly; the retry is - left to the application */ - break; - default: - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - /* unexpected response code */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - GNUNET_break_op (0); - break; - } - ph->pay_cb (ph->pay_cb_cls, - &hr); - } - else - { - GNUNET_assert (0 == strcasecmp (ph->mode, - "abort-refund")); - - switch (response_code) - { - case 0: - hr.ec = TALER_EC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK == - check_abort_refund (ph, - json)) - { - TALER_MERCHANT_pay_cancel (ph); - return; - } - hr.http_status = 0; - hr.ec = TALER_EC_PAY_MERCHANT_INVALID_RESPONSE; - break; - case MHD_HTTP_BAD_REQUEST: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* This should never happen, either us or the - merchant is buggy (or API version conflict); just - pass JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, merchant says one of - the signatures is invalid; as we checked them, - this should never happen, we should pass the JSON - reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the - application */ - break; - case MHD_HTTP_FAILED_DEPENDENCY: - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - /* Nothing really to verify, the merchant is blaming the exchange. - We should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - /* Server had an internal issue; we should retry, - but this API leaves this to the application */ - break; - default: - /* unexpected response code */ - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d\n", - (unsigned int) response_code, - (int) hr.ec); - GNUNET_break_op (0); - break; - } - ph->abort_cb (ph->abort_cb_cls, - &hr, - NULL, - NULL, - 0, - NULL); - } - TALER_MERCHANT_pay_cancel (ph); -} - - -/** - * Issue /pay request. Generic version for the various - * variants of the API. - * - * @param ctx the execution loop context - * @param merchant_url base URL of the merchant's backend - * @param merchant_pub public key of the merchant - * @param num_coins number of coins used to pay - * @param coins array of coins we use to pay - * @param mode mode string to use ("pay" or "abort-refund"). - * @param pay_cb the callback to call when a reply for this - * request is available - * @param pay_cb_cls closure for @a pay_cb - * @param abort_cb callback to call for the abort-refund variant - * @param abort_cb_cls closure for @a abort_cb - * @return a handle for this request - */ -static struct TALER_MERCHANT_Pay * -request_pay_generic ( - struct GNUNET_CURL_Context *ctx, - const char *merchant_url, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const char *order_id, - unsigned int num_coins, - const struct TALER_MERCHANT_PaidCoin *coins, - const char *mode, - TALER_MERCHANT_PayCallback pay_cb, - void *pay_cb_cls, - TALER_MERCHANT_PayRefundCallback abort_cb, - void *abort_cb_cls) -{ - struct TALER_MERCHANT_Pay *ph; - json_t *pay_obj; - json_t *j_coins; - CURL *eh; - struct TALER_Amount total_fee; - struct TALER_Amount total_amount; - - if (0 == num_coins) - { - GNUNET_break (0); - return NULL; - } - j_coins = json_array (); - for (unsigned int i = 0; i<num_coins; i++) - { - json_t *j_coin; - const struct TALER_MERCHANT_PaidCoin *pc = &coins[i]; - struct TALER_Amount fee; - - if (0 > - TALER_amount_subtract (&fee, - &pc->amount_with_fee, - &pc->amount_without_fee)) - { - /* Integer underflow, fee larger than total amount? - This should not happen (client violated API!) */ - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - if (0 == i) - { - total_fee = fee; - total_amount = pc->amount_with_fee; - } - else - { - if ( (0 > - TALER_amount_add (&total_fee, - &total_fee, - &fee)) || - (0 > - TALER_amount_add (&total_amount, - &total_amount, - &pc->amount_with_fee)) ) - { - /* integer overflow */ - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - } - - /* create JSON for this coin */ - j_coin = json_pack ( - "{s:o, s:o," /* contribution/coin_pub */ - " s:s, s:o," /* exchange_url / denom_pub */ - " s:o, s:o}", /* ub_sig / coin_sig */ - "contribution", - TALER_JSON_from_amount (&pc->amount_with_fee), - "coin_pub", - GNUNET_JSON_from_data_auto (&pc->coin_pub), - "exchange_url", - pc->exchange_url, - "denom_pub", - GNUNET_JSON_from_rsa_public_key (pc->denom_pub.rsa_public_key), - "ub_sig", - GNUNET_JSON_from_rsa_signature (pc->denom_sig.rsa_signature), - "coin_sig", - GNUNET_JSON_from_data_auto (&pc->coin_sig)); - if (0 != - json_array_append_new (j_coins, - j_coin)) - { - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - } - - pay_obj = json_pack ("{" - " s:s," /* mode */ - " s:o," /* coins */ - " s:s," /* order_id */ - " s:o," /* merchant_pub */ - "}", - "mode", - mode, - "coins", - j_coins, /* reference consumed! */ - "order_id", - order_id, - "merchant_pub", - GNUNET_JSON_from_data_auto (merchant_pub)); - if (NULL == pay_obj) - { - GNUNET_break (0); - return NULL; - } - ph = GNUNET_new (struct TALER_MERCHANT_Pay); - ph->ctx = ctx; - ph->mode = mode; - ph->abort_cb = abort_cb; - ph->abort_cb_cls = abort_cb_cls; - ph->pay_cb = pay_cb; - ph->pay_cb_cls = pay_cb_cls; - ph->url = TALER_url_join (merchant_url, "pay", NULL); - if (NULL == ph->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - json_decref (pay_obj); - GNUNET_free (ph); - return NULL; - } - ph->num_coins = num_coins; - ph->coins = GNUNET_new_array (num_coins, - struct TALER_MERCHANT_PaidCoin); - memcpy (ph->coins, - coins, - num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); - - eh = curl_easy_init (); - if (GNUNET_OK != TALER_curl_easy_post (&ph->post_ctx, - eh, - pay_obj)) - { - GNUNET_break (0); - json_decref (pay_obj); - GNUNET_free (ph); - return NULL; - } - - json_decref (pay_obj); - GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, - CURLOPT_URL, - ph->url)); - ph->job = GNUNET_CURL_job_add2 (ctx, - eh, - ph->post_ctx.headers, - &handle_pay_finished, - ph); - return ph; -} - - -/** - * Pay a merchant. API for wallets that have the coin's private - * keys. - * _NOTE_: this function does NOT calculate each coin amount in - * order to match the contract total price. This calculation is - * to be made by the logic using this library. - * - * @param ctx the execution loop context - * @param merchant_url base URL of the merchant's backend - * @param h_contract_terms hashcode of the proposal being paid - * @param amount total value of the contract to be paid to the - * merchant - * @param max_fee maximum fee covered by the merchant - * (according to the contract) - * @param merchant_pub the public key of the merchant - * (used to identify the merchant for refund requests) - * @param merchant_sig signature from the merchant over the - * original contract - * @param timestamp timestamp when the contract was finalized, - * must match approximately the current time of the merchant - * @param refund_deadline date until which the merchant can issue - * a refund to the customer via the merchant (can be zero - * if refunds are not allowed) - * @param pay_deadline maximum time limit to pay for this contract - * @param h_wire hash of the merchant’s account details - * @param order_id order id of the proposal being paid - * @param num_coins number of coins used to pay - * @param coins array of coins we use to pay - * @param pay_cb the callback to call when a reply for this - * request is available - * @param pay_cb_cls closure for @a pay_cb - * @return a handle for this request - */ -static struct TALER_MERCHANT_Pay * -prepare_pay_generic (struct GNUNET_CURL_Context *ctx, - const char *merchant_url, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_Amount *amount, - const struct TALER_Amount *max_fee, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_MerchantSignatureP *merchant_sig, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund_deadline, - struct GNUNET_TIME_Absolute pay_deadline, - const struct GNUNET_HashCode *h_wire, - const char *order_id, - unsigned int num_coins, - const struct TALER_MERCHANT_PayCoin *coins, - const char *mode, - TALER_MERCHANT_PayCallback pay_cb, - void *pay_cb_cls, - TALER_MERCHANT_PayRefundCallback abort_cb, - void *abort_cb_cls) -{ - struct TALER_DepositRequestPS dr; - struct TALER_MERCHANT_PaidCoin pc[num_coins]; - - (void) GNUNET_TIME_round_abs (&timestamp); - (void) GNUNET_TIME_round_abs (&pay_deadline); - (void) GNUNET_TIME_round_abs (&refund_deadline); - - if (GNUNET_YES != - TALER_amount_cmp_currency (amount, - max_fee)) - { - GNUNET_break (0); - return NULL; - } - - dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); - dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); - dr.h_contract_terms = *h_contract_terms; - dr.h_wire = *h_wire; - dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); - dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); - dr.merchant = *merchant_pub; - - for (unsigned int i = 0; i<num_coins; i++) - { - const struct TALER_MERCHANT_PayCoin *coin = &coins[i]; // coin priv. - struct TALER_MERCHANT_PaidCoin *p = &pc[i]; // coin pub. - struct TALER_Amount fee; - - /* prepare 'dr' for this coin to generate coin signature */ - GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv, - &dr.coin_pub.eddsa_pub); - TALER_amount_hton (&dr.amount_with_fee, - &coin->amount_with_fee); - if (0 > - TALER_amount_subtract (&fee, - &coin->amount_with_fee, - &coin->amount_without_fee)) - { - /* Integer underflow, fee larger than total amount? - This should not happen (client violated API!) */ - GNUNET_break (0); - return NULL; - } - TALER_amount_hton (&dr.deposit_fee, - &fee); - { - TALER_LOG_DEBUG ("... amount_with_fee was %s\n", - TALER_amount2s (&coin->amount_with_fee)); - TALER_LOG_DEBUG ("... fee was %s\n", - TALER_amount2s (&fee)); - } - - GNUNET_CRYPTO_eddsa_sign (&coin->coin_priv.eddsa_priv, - &dr, - &p->coin_sig.eddsa_signature); - p->denom_pub = coin->denom_pub; - p->denom_sig = coin->denom_sig; - p->denom_value = coin->denom_value; - p->coin_pub = dr.coin_pub; - p->amount_with_fee = coin->amount_with_fee; - p->amount_without_fee = coin->amount_without_fee; - p->refund_fee = coin->refund_fee; - p->exchange_url = coin->exchange_url; - } - return request_pay_generic (ctx, - merchant_url, - merchant_pub, - order_id, - num_coins, - pc, - mode, - pay_cb, - pay_cb_cls, - abort_cb, - abort_cb_cls); -} - - -/** - * Pay a merchant. API for wallets that have the coin's private keys. - * _NOTE_: this function does NOT calculate each coin amount in order - * to match the contract total price. This calculation is to be made - * by the logic using this library. - * - * @param ctx the execution loop context - * @param merchant_url base URL of the merchant's backend - * @param h_contract_terms hashcode of the proposal being paid - * @param amount total value of the contract to be paid to the merchant - * @param max_fee maximum fee covered by the merchant (according to the contract) - * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) - * @param merchant_sig signature from the merchant over the original contract - * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant - * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) - * @param pay_deadline maximum time limit to pay for this contract - * @param h_wire hash of the merchant’s account details - * @param order_id order id of the proposal being paid - * @param num_coins number of coins used to pay - * @param coins array of coins we use to pay - * @param pay_cb the callback to call when a reply for this request is available - * @param pay_cb_cls closure for @a pay_cb - * @return a handle for this request - */ -struct TALER_MERCHANT_Pay * -TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, - const char *merchant_url, - const struct GNUNET_HashCode *h_contract_terms, - const struct TALER_Amount *amount, - const struct TALER_Amount *max_fee, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_MerchantSignatureP *merchant_sig, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund_deadline, - struct GNUNET_TIME_Absolute pay_deadline, - const struct GNUNET_HashCode *h_wire, - const char *order_id, - unsigned int num_coins, - const struct TALER_MERCHANT_PayCoin *coins, - TALER_MERCHANT_PayCallback pay_cb, - void *pay_cb_cls) -{ - return prepare_pay_generic (ctx, - merchant_url, - h_contract_terms, - amount, - max_fee, - merchant_pub, - merchant_sig, - timestamp, - refund_deadline, - pay_deadline, - h_wire, - order_id, - num_coins, - coins, - "pay", - pay_cb, - pay_cb_cls, - NULL, - NULL); -} - - -/** - * Run a payment abort operation, asking for refunds for coins - * that were previously spend on a /pay that failed to go through. - * - * @param ctx execution context - * @param merchant_url base URL of the merchant - * @param h_wire hash of the merchant’s account details - * @param h_contract hash of the contact of the merchant with the customer - * @param transaction_id transaction id for the transaction between merchant and customer - * @param amount total value of the contract to be paid to the merchant - * @param max_fee maximum fee covered by the merchant (according to the contract) - * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) - * @param merchant_sig signature from the merchant over the original contract - * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant - * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) - * @param pay_deadline maximum time limit to pay for this contract - * @param num_coins number of coins used to pay - * @param coins array of coins we use to pay - * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. - * @param payref_cb the callback to call when a reply for this request is available - * @param payref_cb_cls closure for @a pay_cb - * @return a handle for this request - */ -struct TALER_MERCHANT_Pay * -TALER_MERCHANT_pay_abort (struct GNUNET_CURL_Context *ctx, - const char *merchant_url, - const struct GNUNET_HashCode *h_contract, - const struct TALER_Amount *amount, - const struct TALER_Amount *max_fee, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_MerchantSignatureP *merchant_sig, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund_deadline, - struct GNUNET_TIME_Absolute pay_deadline, - const struct GNUNET_HashCode *h_wire, - const char *order_id, - unsigned int num_coins, - const struct TALER_MERCHANT_PayCoin *coins, - TALER_MERCHANT_PayRefundCallback payref_cb, - void *payref_cb_cls) -{ - struct TALER_MERCHANT_Pay *ph; - - ph = prepare_pay_generic (ctx, - merchant_url, - h_contract, - amount, - max_fee, - merchant_pub, - merchant_sig, - timestamp, - refund_deadline, - pay_deadline, - h_wire, - order_id, - num_coins, - coins, - "abort-refund", - NULL, - NULL, - payref_cb, - payref_cb_cls); - if (NULL == ph) - return NULL; - ph->h_contract_terms = *h_contract; - return ph; -} - - -/** - * PAY a merchant. API for frontends talking to backends. Here, - * the frontend does not have the coin's private keys, but just - * the public keys and signatures. Note the subtle difference - * in the type of @a coins compared to #TALER_MERCHANT_pay(). - * - * @param ctx the execution loop context - * @param merchant_url base URL of the merchant's backend - * @param merchant_pub public key of the merchant - * @param num_coins number of coins used to pay - * @param coins array of coins we use to pay - * @param pay_cb the callback to call when a reply for this request is available - * @param pay_cb_cls closure for @a pay_cb - * @return a handle for this request - */ -struct TALER_MERCHANT_Pay * -TALER_MERCHANT_pay_frontend ( - struct GNUNET_CURL_Context *ctx, - const char *merchant_url, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const char *order_id, - unsigned int num_coins, - const struct TALER_MERCHANT_PaidCoin *coins, - TALER_MERCHANT_PayCallback pay_cb, - void *pay_cb_cls) -{ - return request_pay_generic (ctx, - merchant_url, - merchant_pub, - order_id, - num_coins, - coins, - "pay", - pay_cb, - pay_cb_cls, - NULL, - NULL); -} - - -/** - * Cancel a pay permission request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param pay the pay permission request handle - */ -void -TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *pay) -{ - if (NULL != pay->job) - { - GNUNET_CURL_job_cancel (pay->job); - pay->job = NULL; - } - TALER_curl_easy_post_finished (&pay->post_ctx); - GNUNET_free (pay->coins); - GNUNET_free (pay->url); - GNUNET_free (pay); -} - - -/* end of merchant_api_pay.c */ diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c @@ -0,0 +1,1067 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015, 2016, 2017, 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 Foundation; either version 2.1, + or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with TALER; see the file COPYING.LGPL. + If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/merchant_api_pay.c + * @brief Implementation of the /pay request + * of the merchant's HTTP API + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_merchant_service.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_signatures.h> +#include <taler/taler_exchange_service.h> +#include <taler/taler_curl_lib.h> + + +/** + * @brief A Pay Handle + */ +struct TALER_MERCHANT_Pay +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result in "pay" @e mode. + */ + TALER_MERCHANT_OrderPayCallback pay_cb; + + /** + * Closure for @a pay_cb. + */ + void *pay_cb_cls; + + /** + * Function to call with the result in "abort-refund" @e mode. + */ + TALER_MERCHANT_PayRefundCallback abort_cb; + + /** + * Closure for @a abort_cb. + */ + void *abort_cb_cls; + + /** + * Operational mode, either "pay" or "abort-refund". + */ + const char *mode; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * The coins we are paying with. + */ + struct TALER_MERCHANT_PaidCoin *coins; + + /** + * Number of @e coins we are paying with. + */ + unsigned int num_coins; + + /** + * Hash of the contract, only available in "abort-refund" mode. + */ + struct GNUNET_HashCode h_contract_terms; + +}; + + +/** + * Check that the response for a /pay refund is well-formed, + * and call the application callback with the result if it is + * OK. Otherwise returns #GNUNET_SYSERR. + * + * @param ph handle to operation that created the reply + * @param json the reply to parse + * @return #GNUNET_OK on success + */ +static int +check_abort_refund (struct TALER_MERCHANT_Pay *ph, + const json_t *json) +{ + json_t *refunds; + unsigned int num_refunds; + struct TALER_MerchantPublicKeyP merchant_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("refund_permissions", &refunds), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + num_refunds = json_array_size (refunds); + { + struct TALER_MERCHANT_RefundEntry res[GNUNET_NZL (num_refunds)]; + + for (unsigned int i = 0; i<num_refunds; i++) + { + struct TALER_MerchantSignatureP *sig = &res[i].merchant_sig; + json_t *refund = json_array_get (refunds, i); + struct GNUNET_JSON_Specification spec_detail[] = { + GNUNET_JSON_spec_fixed_auto ("merchant_sig", + sig), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &res[i].coin_pub), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &res[i].rtransaction_id), + GNUNET_JSON_spec_end () + }; + int found; + + if (GNUNET_OK != + GNUNET_JSON_parse (refund, + spec_detail, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + found = -1; + for (unsigned int j = 0; j<ph->num_coins; j++) + { + if (0 == memcmp (&ph->coins[j].coin_pub, + &res[i].coin_pub, + sizeof + (struct TALER_CoinSpendPublicKeyP))) + { + found = j; + break; + } + } + if (-1 == found) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + + { + struct TALER_RefundRequestPS rr = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND), + .purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)), + .h_contract_terms = ph->h_contract_terms, + .coin_pub = res[i].coin_pub, + .merchant = merchant_pub, + .rtransaction_id = GNUNET_htonll (res[i].rtransaction_id) + }; + + TALER_amount_hton (&rr.refund_amount, + &ph->coins[found].amount_with_fee); + TALER_amount_hton (&rr.refund_fee, + &ph->coins[found].refund_fee); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, + &rr, + &sig->eddsa_sig, + &merchant_pub.eddsa_pub)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + } + } + { + struct TALER_MERCHANT_HttpResponse hr = { + .reply = json, + .http_status = MHD_HTTP_OK + }; + + ph->abort_cb (ph->abort_cb_cls, + &hr, + &merchant_pub, + &ph->h_contract_terms, + num_refunds, + res); + } + ph->abort_cb = NULL; + } + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * We got a 403 response back from the exchange (or the merchant). + * Now we need to check the provided cryptographic proof that the + * coin was actually already spent! + * + * @param pc handle of the original coin we paid with + * @param json cryptographic proof of coin's transaction + * history as was returned by the exchange/merchant + * @return #GNUNET_OK if proof checks out + */ +static int +check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc, + json_t *json) +{ + struct TALER_Amount spent; + struct TALER_Amount spent_plus_contrib; + + if (GNUNET_OK != + TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */ + pc->amount_with_fee.currency, + &pc->coin_pub, + json, + &spent)) + { + /* Exchange's history fails to verify */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (&spent_plus_contrib, + &spent, + &pc->amount_with_fee)) + { + /* We got an integer overflow? Bad application! */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (-1 != TALER_amount_cmp (&pc->denom_value, + &spent_plus_contrib)) + { + /* according to our calculations, the transaction should + have still worked, exchange error! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Accepting proof of double-spending\n"); + return GNUNET_OK; +} + + +/** + * We got a 409 response back from the exchange (or the merchant). + * Now we need to check the provided cryptographic proof that the + * coin was actually already spent! + * + * @param ph handle of the original pay operation + * @param json cryptographic proof returned by the + * exchange/merchant + * @return #GNUNET_OK if proof checks out + */ +static int +check_conflict (struct TALER_MERCHANT_Pay *ph, + const json_t *json) +{ + json_t *history; + json_t *ereply; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("exchange_reply", &ereply), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), + GNUNET_JSON_spec_end () + }; + struct GNUNET_JSON_Specification hspec[] = { + GNUNET_JSON_spec_json ("history", &history), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_JSON_parse (ereply, + hspec, + NULL, NULL)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec); + + for (unsigned int i = 0; i<ph->num_coins; i++) + { + if (0 == memcmp (&ph->coins[i].coin_pub, + &coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP))) + { + int ret; + + ret = check_coin_history (&ph->coins[i], + history); + GNUNET_JSON_parse_free (hspec); + return ret; + } + } + GNUNET_break_op (0); /* complaint is not about any of the coins + that we actually paid with... */ + GNUNET_JSON_parse_free (hspec); + return GNUNET_SYSERR; +} + + +/** + * Function called when we're done processing the + * HTTP /pay request. + * + * @param cls the `struct TALER_MERCHANT_Pay` + * @param response_code HTTP response code, 0 on error + * @param json response body, NULL if not in JSON + */ +static void +handle_pay_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_Pay *ph = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + ph->job = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "/pay completed with response code %u\n", + (unsigned int) response_code); + if (0 == strcasecmp (ph->mode, + "pay")) + { + switch (response_code) + { + case 0: + hr.ec = TALER_EC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + break; + /* Tolerating Not Acceptable because sometimes + * - especially in tests - we might want to POST + * coins one at a time. */ + case MHD_HTTP_NOT_ACCEPTABLE: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us + * or the merchant is buggy (or API version conflict); + * just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says we tried to abort the payment + * after it was successful. We should pass the JSON reply to the + * application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_PRECONDITION_FAILED: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* Nothing really to verify, the merchant is blaming us for failing to + satisfy some constraint (likely it does not like our exchange because + of some disagreement on the PKI). We should pass the JSON reply to the + application */ + break; + case MHD_HTTP_REQUEST_TIMEOUT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* The merchant couldn't generate a timely response, likely because + it itself waited too long on the exchange. + Pass on to application. */ + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + if (GNUNET_OK != check_conflict (ph, + json)) + { + GNUNET_break_op (0); + response_code = 0; + } + break; + case MHD_HTTP_GONE: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* The merchant says we are too late, the offer has expired or some + denomination key of a coin involved has expired. + Might be a disagreement in timestamps? Still, pass on to application. */ + break; + case MHD_HTTP_FAILED_DEPENDENCY: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* Nothing really to verify, the merchant is blaming the exchange. + We should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + case MHD_HTTP_SERVICE_UNAVAILABLE: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* Exchange couldn't respond properly; the retry is + left to the application */ + break; + default: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + ph->pay_cb (ph->pay_cb_cls, + &hr); + } + else + { + GNUNET_assert (0 == strcasecmp (ph->mode, + "abort-refund")); + + switch (response_code) + { + case 0: + hr.ec = TALER_EC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK == + check_abort_refund (ph, + json)) + { + TALER_MERCHANT_pay_cancel (ph); + return; + } + hr.http_status = 0; + hr.ec = TALER_EC_PAY_MERCHANT_INVALID_RESPONSE; + break; + case MHD_HTTP_BAD_REQUEST: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us or the + merchant is buggy (or API version conflict); just + pass JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_FORBIDDEN: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, merchant says one of + the signatures is invalid; as we checked them, + this should never happen, we should pass the JSON + reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the + application */ + break; + case MHD_HTTP_FAILED_DEPENDENCY: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + /* Nothing really to verify, the merchant is blaming the exchange. + We should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, + but this API leaves this to the application */ + break; + default: + /* unexpected response code */ + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) hr.ec); + GNUNET_break_op (0); + break; + } + ph->abort_cb (ph->abort_cb_cls, + &hr, + NULL, + NULL, + 0, + NULL); + } + TALER_MERCHANT_pay_cancel (ph); +} + + +/** + * Issue /pay request. Generic version for the various + * variants of the API. + * + * @param ctx the execution loop context + * @param merchant_url base URL of the merchant's backend + * @param merchant_pub public key of the merchant + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param mode mode string to use ("pay" or "abort-refund"). + * @param pay_cb the callback to call when a reply for this + * request is available + * @param pay_cb_cls closure for @a pay_cb + * @param abort_cb callback to call for the abort-refund variant + * @param abort_cb_cls closure for @a abort_cb + * @return a handle for this request + */ +static struct TALER_MERCHANT_Pay * +request_pay_generic ( + struct GNUNET_CURL_Context *ctx, + const char *merchant_url, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *order_id, + unsigned int num_coins, + const struct TALER_MERCHANT_PaidCoin *coins, + const char *mode, + TALER_MERCHANT_OrderPayCallback pay_cb, + void *pay_cb_cls, + TALER_MERCHANT_PayRefundCallback abort_cb, + void *abort_cb_cls) +{ + struct TALER_MERCHANT_Pay *ph; + json_t *pay_obj; + json_t *j_coins; + CURL *eh; + struct TALER_Amount total_fee; + struct TALER_Amount total_amount; + + if (0 == num_coins) + { + GNUNET_break (0); + return NULL; + } + j_coins = json_array (); + for (unsigned int i = 0; i<num_coins; i++) + { + json_t *j_coin; + const struct TALER_MERCHANT_PaidCoin *pc = &coins[i]; + struct TALER_Amount fee; + + if (0 > + TALER_amount_subtract (&fee, + &pc->amount_with_fee, + &pc->amount_without_fee)) + { + /* Integer underflow, fee larger than total amount? + This should not happen (client violated API!) */ + GNUNET_break (0); + json_decref (j_coins); + return NULL; + } + if (0 == i) + { + total_fee = fee; + total_amount = pc->amount_with_fee; + } + else + { + if ( (0 > + TALER_amount_add (&total_fee, + &total_fee, + &fee)) || + (0 > + TALER_amount_add (&total_amount, + &total_amount, + &pc->amount_with_fee)) ) + { + /* integer overflow */ + GNUNET_break (0); + json_decref (j_coins); + return NULL; + } + } + + /* create JSON for this coin */ + j_coin = json_pack ( + "{s:o, s:o," /* contribution/coin_pub */ + " s:s, s:o," /* exchange_url / denom_pub */ + " s:o, s:o}", /* ub_sig / coin_sig */ + "contribution", + TALER_JSON_from_amount (&pc->amount_with_fee), + "coin_pub", + GNUNET_JSON_from_data_auto (&pc->coin_pub), + "exchange_url", + pc->exchange_url, + "denom_pub", + GNUNET_JSON_from_rsa_public_key (pc->denom_pub.rsa_public_key), + "ub_sig", + GNUNET_JSON_from_rsa_signature (pc->denom_sig.rsa_signature), + "coin_sig", + GNUNET_JSON_from_data_auto (&pc->coin_sig)); + if (0 != + json_array_append_new (j_coins, + j_coin)) + { + GNUNET_break (0); + json_decref (j_coins); + return NULL; + } + } + + pay_obj = json_pack ("{" + " s:s," /* mode */ + " s:o," /* coins */ + " s:s," /* order_id */ + " s:o," /* merchant_pub */ + "}", + "mode", + mode, + "coins", + j_coins, /* reference consumed! */ + "order_id", + order_id, + "merchant_pub", + GNUNET_JSON_from_data_auto (merchant_pub)); + if (NULL == pay_obj) + { + GNUNET_break (0); + return NULL; + } + ph = GNUNET_new (struct TALER_MERCHANT_Pay); + ph->ctx = ctx; + ph->mode = mode; + ph->abort_cb = abort_cb; + ph->abort_cb_cls = abort_cb_cls; + ph->pay_cb = pay_cb; + ph->pay_cb_cls = pay_cb_cls; + ph->url = TALER_url_join (merchant_url, "pay", NULL); + if (NULL == ph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + json_decref (pay_obj); + GNUNET_free (ph); + return NULL; + } + ph->num_coins = num_coins; + ph->coins = GNUNET_new_array (num_coins, + struct TALER_MERCHANT_PaidCoin); + memcpy (ph->coins, + coins, + num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); + + eh = curl_easy_init (); + if (GNUNET_OK != TALER_curl_easy_post (&ph->post_ctx, + eh, + pay_obj)) + { + GNUNET_break (0); + json_decref (pay_obj); + GNUNET_free (ph); + return NULL; + } + + json_decref (pay_obj); + GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, + CURLOPT_URL, + ph->url)); + ph->job = GNUNET_CURL_job_add2 (ctx, + eh, + ph->post_ctx.headers, + &handle_pay_finished, + ph); + return ph; +} + + +/** + * Pay a merchant. API for wallets that have the coin's private + * keys. + * _NOTE_: this function does NOT calculate each coin amount in + * order to match the contract total price. This calculation is + * to be made by the logic using this library. + * + * @param ctx the execution loop context + * @param merchant_url base URL of the merchant's backend + * @param h_contract_terms hashcode of the proposal being paid + * @param amount total value of the contract to be paid to the + * merchant + * @param max_fee maximum fee covered by the merchant + * (according to the contract) + * @param merchant_pub the public key of the merchant + * (used to identify the merchant for refund requests) + * @param merchant_sig signature from the merchant over the + * original contract + * @param timestamp timestamp when the contract was finalized, + * must match approximately the current time of the merchant + * @param refund_deadline date until which the merchant can issue + * a refund to the customer via the merchant (can be zero + * if refunds are not allowed) + * @param pay_deadline maximum time limit to pay for this contract + * @param h_wire hash of the merchant’s account details + * @param order_id order id of the proposal being paid + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param pay_cb the callback to call when a reply for this + * request is available + * @param pay_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +static struct TALER_MERCHANT_Pay * +prepare_pay_generic (struct GNUNET_CURL_Context *ctx, + const char *merchant_url, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_Amount *amount, + const struct TALER_Amount *max_fee, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantSignatureP *merchant_sig, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + struct GNUNET_TIME_Absolute pay_deadline, + const struct GNUNET_HashCode *h_wire, + const char *order_id, + unsigned int num_coins, + const struct TALER_MERCHANT_PayCoin *coins, + const char *mode, + TALER_MERCHANT_PayCallback pay_cb, + void *pay_cb_cls, + TALER_MERCHANT_PayRefundCallback abort_cb, + void *abort_cb_cls) +{ + struct TALER_DepositRequestPS dr; + struct TALER_MERCHANT_PaidCoin pc[num_coins]; + + (void) GNUNET_TIME_round_abs (&timestamp); + (void) GNUNET_TIME_round_abs (&pay_deadline); + (void) GNUNET_TIME_round_abs (&refund_deadline); + + if (GNUNET_YES != + TALER_amount_cmp_currency (amount, + max_fee)) + { + GNUNET_break (0); + return NULL; + } + + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); + dr.h_contract_terms = *h_contract_terms; + dr.h_wire = *h_wire; + dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + dr.merchant = *merchant_pub; + + for (unsigned int i = 0; i<num_coins; i++) + { + const struct TALER_MERCHANT_PayCoin *coin = &coins[i]; // coin priv. + struct TALER_MERCHANT_PaidCoin *p = &pc[i]; // coin pub. + struct TALER_Amount fee; + + /* prepare 'dr' for this coin to generate coin signature */ + GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv, + &dr.coin_pub.eddsa_pub); + TALER_amount_hton (&dr.amount_with_fee, + &coin->amount_with_fee); + if (0 > + TALER_amount_subtract (&fee, + &coin->amount_with_fee, + &coin->amount_without_fee)) + { + /* Integer underflow, fee larger than total amount? + This should not happen (client violated API!) */ + GNUNET_break (0); + return NULL; + } + TALER_amount_hton (&dr.deposit_fee, + &fee); + { + TALER_LOG_DEBUG ("... amount_with_fee was %s\n", + TALER_amount2s (&coin->amount_with_fee)); + TALER_LOG_DEBUG ("... fee was %s\n", + TALER_amount2s (&fee)); + } + + GNUNET_CRYPTO_eddsa_sign (&coin->coin_priv.eddsa_priv, + &dr, + &p->coin_sig.eddsa_signature); + p->denom_pub = coin->denom_pub; + p->denom_sig = coin->denom_sig; + p->denom_value = coin->denom_value; + p->coin_pub = dr.coin_pub; + p->amount_with_fee = coin->amount_with_fee; + p->amount_without_fee = coin->amount_without_fee; + p->refund_fee = coin->refund_fee; + p->exchange_url = coin->exchange_url; + } + return request_pay_generic (ctx, + merchant_url, + merchant_pub, + order_id, + num_coins, + pc, + mode, + pay_cb, + pay_cb_cls, + abort_cb, + abort_cb_cls); +} + + +/** + * Pay a merchant. API for wallets that have the coin's private keys. + * _NOTE_: this function does NOT calculate each coin amount in order + * to match the contract total price. This calculation is to be made + * by the logic using this library. + * + * @param ctx the execution loop context + * @param merchant_url base URL of the merchant's backend + * @param h_contract_terms hashcode of the proposal being paid + * @param amount total value of the contract to be paid to the merchant + * @param max_fee maximum fee covered by the merchant (according to the contract) + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param merchant_sig signature from the merchant over the original contract + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant + * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) + * @param pay_deadline maximum time limit to pay for this contract + * @param h_wire hash of the merchant’s account details + * @param order_id order id of the proposal being paid + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param pay_cb the callback to call when a reply for this request is available + * @param pay_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_Pay * +TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, + const char *merchant_url, + const struct GNUNET_HashCode *h_contract_terms, + const struct TALER_Amount *amount, + const struct TALER_Amount *max_fee, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantSignatureP *merchant_sig, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + struct GNUNET_TIME_Absolute pay_deadline, + const struct GNUNET_HashCode *h_wire, + const char *order_id, + unsigned int num_coins, + const struct TALER_MERCHANT_PayCoin *coins, + TALER_MERCHANT_PayCallback pay_cb, + void *pay_cb_cls) +{ + return prepare_pay_generic (ctx, + merchant_url, + h_contract_terms, + amount, + max_fee, + merchant_pub, + merchant_sig, + timestamp, + refund_deadline, + pay_deadline, + h_wire, + order_id, + num_coins, + coins, + "pay", + pay_cb, + pay_cb_cls, + NULL, + NULL); +} + + +/** + * Run a payment abort operation, asking for refunds for coins + * that were previously spend on a /pay that failed to go through. + * + * @param ctx execution context + * @param merchant_url base URL of the merchant + * @param h_wire hash of the merchant’s account details + * @param h_contract hash of the contact of the merchant with the customer + * @param transaction_id transaction id for the transaction between merchant and customer + * @param amount total value of the contract to be paid to the merchant + * @param max_fee maximum fee covered by the merchant (according to the contract) + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param merchant_sig signature from the merchant over the original contract + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant + * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) + * @param pay_deadline maximum time limit to pay for this contract + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param payref_cb the callback to call when a reply for this request is available + * @param payref_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_Pay * +TALER_MERCHANT_pay_abort (struct GNUNET_CURL_Context *ctx, + const char *merchant_url, + const struct GNUNET_HashCode *h_contract, + const struct TALER_Amount *amount, + const struct TALER_Amount *max_fee, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantSignatureP *merchant_sig, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund_deadline, + struct GNUNET_TIME_Absolute pay_deadline, + const struct GNUNET_HashCode *h_wire, + const char *order_id, + unsigned int num_coins, + const struct TALER_MERCHANT_PayCoin *coins, + TALER_MERCHANT_PayRefundCallback payref_cb, + void *payref_cb_cls) +{ + struct TALER_MERCHANT_Pay *ph; + + ph = prepare_pay_generic (ctx, + merchant_url, + h_contract, + amount, + max_fee, + merchant_pub, + merchant_sig, + timestamp, + refund_deadline, + pay_deadline, + h_wire, + order_id, + num_coins, + coins, + "abort-refund", + NULL, + NULL, + payref_cb, + payref_cb_cls); + if (NULL == ph) + return NULL; + ph->h_contract_terms = *h_contract; + return ph; +} + + +/** + * PAY a merchant. API for frontends talking to backends. Here, + * the frontend does not have the coin's private keys, but just + * the public keys and signatures. Note the subtle difference + * in the type of @a coins compared to #TALER_MERCHANT_pay(). + * + * @param ctx the execution loop context + * @param merchant_url base URL of the merchant's backend + * @param merchant_pub public key of the merchant + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param pay_cb the callback to call when a reply for this request is available + * @param pay_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_Pay * +TALER_MERCHANT_pay_frontend ( + struct GNUNET_CURL_Context *ctx, + const char *merchant_url, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *order_id, + unsigned int num_coins, + const struct TALER_MERCHANT_PaidCoin *coins, + TALER_MERCHANT_PayCallback pay_cb, + void *pay_cb_cls) +{ + return request_pay_generic (ctx, + merchant_url, + merchant_pub, + order_id, + num_coins, + coins, + "pay", + pay_cb, + pay_cb_cls, + NULL, + NULL); +} + + +/** + * Cancel a pay permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param pay the pay permission request handle + */ +void +TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *pay) +{ + if (NULL != pay->job) + { + GNUNET_CURL_job_cancel (pay->job); + pay->job = NULL; + } + TALER_curl_easy_post_finished (&pay->post_ctx); + GNUNET_free (pay->coins); + GNUNET_free (pay->url); + GNUNET_free (pay); +} + + +/* end of merchant_api_pay.c */ diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am @@ -31,9 +31,6 @@ libtalermerchanttesting_la_SOURCES = \ \ testing_api_cmd_check_payment.c \ testing_api_cmd_history.c \ - testing_api_cmd_pay.c \ - testing_api_cmd_pay_abort.c \ - testing_api_cmd_pay_abort_refund.c \ testing_api_cmd_poll_payment.c \ testing_api_cmd_refund_increase.c \ testing_api_cmd_refund_lookup.c \ diff --git a/src/testing/testing_api_trait_refund_entry.c b/src/testing/testing_api_trait_refund_entry.c @@ -29,6 +29,8 @@ #include <taler/taler_testing_lib.h> #include "taler_merchant_service.h" +// FIXME: rename: entry->detail! + #define TALER_TESTING_TRAIT_REFUND_ENTRY "refund-entry" /** @@ -41,10 +43,10 @@ * @return #GNUNET_OK on success */ int -TALER_TESTING_get_trait_refund_entry - (const struct TALER_TESTING_Command *cmd, +TALER_TESTING_get_trait_refund_entry ( + const struct TALER_TESTING_Command *cmd, unsigned int index, - const struct TALER_MERCHANT_RefundEntry **refund_entry) + const struct TALER_MERCHANT_RefundDetail **refund_entry) { return cmd->traits (cmd->cls, (const void **) refund_entry, @@ -62,9 +64,9 @@ TALER_TESTING_get_trait_refund_entry * @return the trait */ struct TALER_TESTING_Trait -TALER_TESTING_make_trait_refund_entry - (unsigned int index, - const struct TALER_MERCHANT_RefundEntry *refund_entry) +TALER_TESTING_make_trait_refund_entry ( + unsigned int index, + const struct TALER_MERCHANT_RefundDetail *refund_entry) { struct TALER_TESTING_Trait ret = { .index = index,