From d8e64ae258b911d4243a0fd62e46585f54bb445b Mon Sep 17 00:00:00 2001 From: Jonathan Buchanan Date: Mon, 17 Aug 2020 21:03:45 -0400 Subject: testing and lib sources for new endpoint --- src/backend/taler-merchant-httpd_get-orders-ID.c | 41 ++ src/include/taler_merchant_service.h | 144 ++++--- src/include/taler_merchant_testing_lib.h | 29 +- src/lib/Makefile.am | 3 +- src/lib/merchant_api_wallet_get_order.c | 182 +-------- src/lib/merchant_api_wallet_post_order_refund.c | 448 +++++++++++++++++++++ src/testing/Makefile.am | 1 + src/testing/test_merchant_api.c | 16 +- src/testing/testing_api_cmd_wallet_get_order.c | 89 +--- .../testing_api_cmd_wallet_post_orders_refund.c | 316 +++++++++++++++ 10 files changed, 950 insertions(+), 319 deletions(-) create mode 100644 src/lib/merchant_api_wallet_post_order_refund.c create mode 100644 src/testing/testing_api_cmd_wallet_post_orders_refund.c diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c b/src/backend/taler-merchant-httpd_get-orders-ID.c index 62be05e8..9e67f92c 100644 --- a/src/backend/taler-merchant-httpd_get-orders-ID.c +++ b/src/backend/taler-merchant-httpd_get-orders-ID.c @@ -693,6 +693,7 @@ check_resume_god (struct GetOrderData *god) } +//#if 0 /** * Callbacks of this type are used to serve the result of submitting a * refund request to an exchange. @@ -742,8 +743,10 @@ refund_cb (void *cls, } check_resume_god (cr->god); } +//#endif +//#if 0 /** * Function called with the result of a #TMH_EXCHANGES_find_exchange() * operation. @@ -784,6 +787,7 @@ exchange_found_cb (void *cls, cr->exchange_reply = json_incref ((json_t*) hr->reply); check_resume_god (cr->god); } +//#endif /** @@ -1000,6 +1004,22 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh, } } +#if 0 + { + const char *await_refund_obtained_s; + + await_refund_obtained_s = + MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "await_refund_obtained"); + + god->sc.awaiting_refund_obtained = + (NULL != await_refund_obtained_s) + ? 0 == strcasecmp (await_refund_obtained_s, "yes") + : false; + } +#endif + { const char *min_refund; @@ -1308,6 +1328,7 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh, "Failed to lookup refunds for contract"); } +//#if 0 /* Now launch exchange interactions, unless we already have the response in the database! */ for (struct CoinRefund *cr = god->cr_head; @@ -1342,6 +1363,26 @@ TMH_get_orders_ID (const struct TMH_RequestHandler *rh, break; } } +//#endif +#if 0 + if ( (god->sc.awaiting_refund_obtained) && + (god->refund_available)) + { + /* Client is waiting for pending refunds to be picked up, suspend + until timeout */ + struct GNUNET_TIME_Relative remaining; + + remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); + if (0 != remaining.rel_value_us) + { + /* yes, indeed suspend */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting pending refunds\n"); + suspend_god (god); + return MHD_YES; + } + } +#endif if ( (god->sc.awaiting_refund) && ( (! god->refunded) || diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index 46d14ead..2da5e03c 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -1455,48 +1455,6 @@ TALER_MERCHANT_orders_get_cancel ( struct TALER_MERCHANT_OrderWalletGetHandle; -/** - * 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; - - /** - * 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 /orders/$ID request * @@ -1513,9 +1471,6 @@ struct TALER_MERCHANT_RefundDetail * the payment * @param already_paid_order_id equivalent order that this customer * paid already, or NULL for none - * @param merchant_pub public key of the merchant - * @param num_refunds length of the @a refunds array - * @param refunds details about the refund processing */ typedef void (*TALER_MERCHANT_OrderWalletGetCallback) ( @@ -1525,10 +1480,7 @@ typedef void enum GNUNET_GenericReturnValue refunded, struct TALER_Amount *refund_amount, const char *taler_pay_uri, - const char *already_paid_order_id, - const struct TALER_MerchantPublicKeyP *merchant_pub, - unsigned int num_refunds, - const struct TALER_MERCHANT_RefundDetail *refunds); + const char *already_paid_order_id); /** @@ -2433,6 +2385,100 @@ TALER_MERCHANT_post_order_refund_cancel ( struct TALER_MERCHANT_OrderRefundHandle *orh); +/** + * Handle for a (public) POST /orders/ID/refund operation. + */ +struct TALER_MERCHANT_WalletOrderRefundHandle; + + +/** + * 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; + + /** + * 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 (public) POST /orders/ID/refund request + * + * @param cls closure + * @param http_status HTTP status code for this request + * @param ec taler-specific error code + */ +typedef void +(*TALER_MERCHANT_WalletRefundCallback) ( + void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + const struct TALER_Amount *refund_amount, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct TALER_MERCHANT_RefundDetail refunds[], + unsigned int refunds_length); + + +/** + * Obtain the refunds that have been granted for an order. + * + * @param ctx the CURL context used to connect to the backend + * @param backend_url backend's base URL, including final "/" + * @param order_id id of the order whose refund is to be increased + * @param h_contract_terms hash of the contract terms of the order + * @param cb callback processing the response from /refund + * @param cb_cls closure for cb + */ +struct TALER_MERCHANT_WalletOrderRefundHandle * +TALER_MERCHANT_wallet_post_order_refund ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *order_id, + const struct GNUNET_HashCode *h_contract_terms, + TALER_MERCHANT_WalletRefundCallback cb, + void *cb_cls); + +/** + * Cancel a (public) POST /refund request. + * + * @param orh the refund operation to cancel + */ +void +TALER_MERCHANT_wallet_post_order_refund_cancel ( + struct TALER_MERCHANT_WalletOrderRefundHandle *orh); + + /* ********************* /transfers *********************** */ /** diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h index a9aa2b48..5618b441 100644 --- a/src/include/taler_merchant_testing_lib.h +++ b/src/include/taler_merchant_testing_lib.h @@ -601,10 +601,6 @@ TALER_TESTING_cmd_poll_orders_conclude (const char *label, * @param paid whether the order has been paid for or not. * @param refunded whether the order has been refunded. * @param http_status expected HTTP response code for the request. - * @param ... NULL-terminated list of labels (const char *) of - * refunds (commands) we expect to be aggregated in the transfer - * (assuming @a http_code is #MHD_HTTP_OK). If @e refunded is false, - * this parameter is ignored. */ struct TALER_TESTING_Command TALER_TESTING_cmd_wallet_get_order (const char *label, @@ -612,8 +608,7 @@ TALER_TESTING_cmd_wallet_get_order (const char *label, const char *order_reference, bool paid, bool refunded, - unsigned int http_status, - ...); + unsigned int http_status); /** @@ -819,6 +814,28 @@ TALER_TESTING_cmd_merchant_order_refund (const char *label, unsigned int http_code); +/** + * Define a "refund order" CMD. + * + * @param label command label. + * @param merchant_url base URL of the backend serving the + * "refund increase" request. + * @param order_ref order id of the contract to refund. + * @param http_code expected HTTP response code. + * @param ... NULL-terminated list of labels (const char *) of + * refunds (commands) we expect to be aggregated in the transfer + * (assuming @a http_code is #MHD_HTTP_OK). If @e refunded is false, + * this parameter is ignored. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_wallet_order_refund (const char *label, + const char *merchant_url, + const char *order_ref, + unsigned int http_code, + ...); + + /** * Define a "DELETE order" CMD. * diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index a2218be1..8b938706 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -49,7 +49,8 @@ libtalermerchant_la_SOURCES = \ merchant_api_tip_pickup.c \ merchant_api_tip_pickup2.c \ merchant_api_wallet_get_tip.c \ - merchant_api_wallet_get_order.c + merchant_api_wallet_get_order.c \ + merchant_api_wallet_post_order_refund.c libtalermerchant_la_LIBADD = \ -ltalerexchange \ diff --git a/src/lib/merchant_api_wallet_get_order.c b/src/lib/merchant_api_wallet_get_order.c index 7bfdf709..f7ce538c 100644 --- a/src/lib/merchant_api_wallet_get_order.c +++ b/src/lib/merchant_api_wallet_get_order.c @@ -89,9 +89,6 @@ cb_failure (struct TALER_MERCHANT_OrderWalletGetHandle *owgh, GNUNET_SYSERR, NULL, NULL, - NULL, - NULL, - 0, NULL); } @@ -116,22 +113,20 @@ handle_wallet_get_order_finished (void *cls, { case MHD_HTTP_OK: { + /* FIXME: do something with refund_pending */ struct TALER_Amount refund_amount; - json_t *refunds; bool refunded; - struct TALER_MerchantPublicKeyP merchant_pub; - unsigned int refund_len; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_bool ("refunded", &refunded), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &merchant_pub), TALER_JSON_spec_amount ("refund_amount", &refund_amount), - GNUNET_JSON_spec_json ("refunds", - &refunds), GNUNET_JSON_spec_end () }; + struct TALER_MERCHANT_HttpResponse hr = { + .reply = json, + .http_status = MHD_HTTP_OK + }; if (GNUNET_OK != GNUNET_JSON_parse (json, @@ -146,158 +141,13 @@ handle_wallet_get_order_finished (void *cls, return; } - if (! json_is_array (refunds)) - { - GNUNET_break_op (0); - cb_failure (owgh, - TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED, - json); - GNUNET_JSON_parse_free (spec); - TALER_MERCHANT_wallet_order_get_cancel (owgh); - return; - } - - refund_len = json_array_size (refunds); - { - struct TALER_MERCHANT_RefundDetail rds[refund_len]; - - memset (rds, - 0, - sizeof (rds)); - for (unsigned int i = 0; iexchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rd->exchange_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &rd->rtransaction_id), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rd->coin_pub), - TALER_JSON_spec_amount ("refund_amount", - &rd->refund_amount), - GNUNET_JSON_spec_end () - }; - - ret = GNUNET_JSON_parse (jrefund, - rspec, - NULL, NULL); - if (GNUNET_OK == ret) - { - /* check that type field is correct */ - if (0 != strcmp ("success", refund_status_type)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - } - } - } - else - { - struct GNUNET_JSON_Specification rspec[] = { - GNUNET_JSON_spec_string ("type", - &refund_status_type), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rd->coin_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &rd->rtransaction_id), - TALER_JSON_spec_amount ("refund_amount", - &rd->refund_amount), - GNUNET_JSON_spec_end () - }; - - ret = GNUNET_JSON_parse (jrefund, - rspec, - NULL, NULL); - if (GNUNET_OK == ret) - { - /* parse optional arguments */ - json_t *jec; - - jec = json_object_get (jrefund, - "exchange_code"); - if (NULL != jec) - { - if (! json_is_integer (jec)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - } - else - { - rd->hr.ec = (enum TALER_ErrorCode) json_integer_value (jec); - } - } - rd->hr.reply = json_object_get (jrefund, - "exchange_reply"); - /* check that type field is correct */ - if (0 != strcmp ("failure", refund_status_type)) - { - GNUNET_break_op (0); - ret = GNUNET_SYSERR; - } - } - } - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - cb_failure (owgh, - TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED, - json); - TALER_MERCHANT_wallet_order_get_cancel (owgh); - return; - } - rd->hr.http_status = exchange_status; - } - - { - struct TALER_MERCHANT_HttpResponse hr = { - .reply = json, - .http_status = MHD_HTTP_OK - }; - - owgh->cb (owgh->cb_cls, - &hr, - GNUNET_YES, - refunded ? GNUNET_YES : GNUNET_NO, - refunded ? &refund_amount : NULL, - NULL, /* paid! */ - NULL, /* paid! */ - &merchant_pub, - refund_len, - rds); - } - } + owgh->cb (owgh->cb_cls, + &hr, + GNUNET_YES, + refunded ? GNUNET_YES : GNUNET_NO, + refunded ? &refund_amount : NULL, + NULL, /* paid! */ + NULL);/* paid! */ GNUNET_JSON_parse_free (spec); break; } @@ -328,10 +178,7 @@ handle_wallet_get_order_finished (void *cls, GNUNET_NO, NULL, taler_pay_uri, - already_paid, - NULL, - 0, - NULL); + already_paid); } break; } @@ -353,9 +200,6 @@ handle_wallet_get_order_finished (void *cls, GNUNET_SYSERR, NULL, NULL, - NULL, - NULL, - 0, NULL); break; } diff --git a/src/lib/merchant_api_wallet_post_order_refund.c b/src/lib/merchant_api_wallet_post_order_refund.c new file mode 100644 index 00000000..2c63dc17 --- /dev/null +++ b/src/lib/merchant_api_wallet_post_order_refund.c @@ -0,0 +1,448 @@ +/* + This file is part of TALER + Copyright (C) 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 + +*/ +/** + * @file lib/merchant_api_wallet_post_order_refund.c + * @brief Implementation of the (public) POST /orders/ID/refund request + * @author Jonathan Buchanan + */ +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include +#include "taler_merchant_service.h" +#include +#include +#include + + +/** + * Handle for a (public) POST /orders/ID/refund operation. + */ +struct TALER_MERCHANT_WalletOrderRefundHandle +{ + /** + * Complete URL where the backend offers /refund + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * The CURL context to connect to the backend + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The callback to pass the backend response to + */ + TALER_MERCHANT_WalletRefundCallback cb; + + /** + * Clasure to pass to the callback + */ + void *cb_cls; + + /** + * Handle for the request + */ + struct GNUNET_CURL_Job *job; +}; + + +/** + * Convenience function to call the callback in @a owgh with an error code of + * @a ec and the exchange body being set to @a reply. + * + * @param owgh handle providing callback + * @param ec error code to return to application + * @param reply JSON reply we got from the exchange, can be NULL + */ +static void +cb_failure (struct TALER_MERCHANT_WalletOrderRefundHandle *orh, + enum TALER_ErrorCode ec, + const json_t *reply) +{ + struct TALER_MERCHANT_HttpResponse hr = { + .ec = TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED, + .reply = reply + }; + + orh->cb (orh->cb_cls, + &hr, + NULL, + NULL, + NULL, + 0); +} + + +/** + * Callback to process (public) POST /orders/ID/refund response + * + * @param cls the `struct TALER_MERCHANT_OrderRefundHandle` + * @param response_code HTTP response code, 0 on error + * @param json response body, NULL if not JSON + */ +static void +handle_refund_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_MERCHANT_WalletOrderRefundHandle *orh = cls; + const json_t *json = response; + struct TALER_MERCHANT_HttpResponse hr = { + .http_status = (unsigned int) response_code, + .reply = json + }; + + orh->job = NULL; + + switch (response_code) + { + case 0: + hr.ec = TALER_EC_INVALID_RESPONSE; + orh->cb (orh->cb_cls, + &hr, + NULL, + NULL, + NULL, + 0); + break; + case MHD_HTTP_OK: + { + struct TALER_Amount refund_amount; + json_t *refunds; + struct TALER_MerchantPublicKeyP merchant_pub; + unsigned int refund_len; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("refund_amount", + &refund_amount), + GNUNET_JSON_spec_json ("refunds", + &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); + cb_failure (orh, + TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED, + json); + TALER_MERCHANT_wallet_post_order_refund_cancel (orh); + return; + } + + if (! json_is_array (refunds)) + { + GNUNET_break_op (0); + cb_failure (orh, + TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED, + json); + GNUNET_JSON_parse_free (spec); + TALER_MERCHANT_wallet_post_order_refund_cancel (orh); + return; + } + + refund_len = json_array_size (refunds); + { + struct TALER_MERCHANT_RefundDetail rds[refund_len]; + + memset (rds, + 0, + sizeof (rds)); + for (unsigned int i = 0; iexchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rd->exchange_pub), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rd->rtransaction_id), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rd->coin_pub), + TALER_JSON_spec_amount ("refund_amount", + &rd->refund_amount), + GNUNET_JSON_spec_end () + }; + + ret = GNUNET_JSON_parse (jrefund, + rspec, + NULL, NULL); + if (GNUNET_OK == ret) + { + /* check that type field is correct */ + if (0 != strcmp ("success", refund_status_type)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + } + } + } + else + { + struct GNUNET_JSON_Specification rspec[] = { + GNUNET_JSON_spec_string ("type", + &refund_status_type), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rd->coin_pub), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rd->rtransaction_id), + TALER_JSON_spec_amount ("refund_amount", + &rd->refund_amount), + GNUNET_JSON_spec_end () + }; + + ret = GNUNET_JSON_parse (jrefund, + rspec, + NULL, NULL); + if (GNUNET_OK == ret) + { + /* parse optional arguments */ + json_t *jec; + + jec = json_object_get (jrefund, + "exchange_code"); + if (NULL != jec) + { + if (! json_is_integer (jec)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + } + else + { + rd->hr.ec = (enum TALER_ErrorCode) json_integer_value (jec); + } + } + rd->hr.reply = json_object_get (jrefund, + "exchange_reply"); + /* check that type field is correct */ + if (0 != strcmp ("failure", refund_status_type)) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + } + } + } + if (GNUNET_OK != ret) + { + GNUNET_break_op (0); + cb_failure (orh, + TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED, + json); + TALER_MERCHANT_wallet_post_order_refund_cancel (orh); + return; + } + rd->hr.http_status = exchange_status; + } + + { + struct TALER_MERCHANT_HttpResponse hr = { + .reply = json, + .http_status = MHD_HTTP_OK + }; + + orh->cb (orh->cb_cls, + &hr, + &refund_amount, + &merchant_pub, + rds, + refund_len); + } + } + GNUNET_JSON_parse_free (spec); + } + break; + case MHD_HTTP_NO_CONTENT: + orh->cb (orh->cb_cls, + &hr, + NULL, + NULL, + NULL, + 0); + break; + case MHD_HTTP_CONFLICT: + case MHD_HTTP_NOT_FOUND: + hr.ec = TALER_JSON_get_error_code (json); + hr.hint = TALER_JSON_get_error_hint (json); + orh->cb (orh->cb_cls, + &hr, + NULL, + NULL, + NULL, + 0); + break; + default: + GNUNET_break_op (0); /* unexpected status code */ + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &hr); + orh->cb (orh->cb_cls, + &hr, + NULL, + NULL, + NULL, + 0); + break; + } + TALER_MERCHANT_wallet_post_order_refund_cancel (orh); +} + + +/** + * Obtain the refunds that have been granted for an order. + * + * @param ctx the CURL context used to connect to the backend + * @param backend_url backend's base URL, including final "/" + * @param order_id id of the order whose refund is to be increased + * @param h_contract_terms hash of the contract terms of the order + * @param cb callback processing the response from /refund + * @param cb_cls closure for cb + */ +struct TALER_MERCHANT_WalletOrderRefundHandle * +TALER_MERCHANT_wallet_post_order_refund ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *order_id, + const struct GNUNET_HashCode *h_contract_terms, + TALER_MERCHANT_WalletRefundCallback cb, + void *cb_cls) +{ + struct TALER_MERCHANT_WalletOrderRefundHandle *orh; + json_t *req; + CURL *eh; + + orh = GNUNET_new (struct TALER_MERCHANT_WalletOrderRefundHandle); + orh->ctx = ctx; + orh->cb = cb; + orh->cb_cls = cb_cls; + { + char *path; + + GNUNET_asprintf (&path, + "orders/%s/refund", + order_id); + orh->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == orh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (orh); + return NULL; + } + req = json_pack ("{s:o}", + "h_contract", + GNUNET_JSON_from_data_auto (h_contract_terms)); + GNUNET_assert (NULL != req); + eh = curl_easy_init (); + GNUNET_assert (NULL != eh); + if (GNUNET_OK != + TALER_curl_easy_post (&orh->post_ctx, + eh, + req)) + { + GNUNET_break (0); + json_decref (req); + GNUNET_free (orh->url); + GNUNET_free (orh); + return NULL; + } + json_decref (req); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + orh->url)); + orh->job = GNUNET_CURL_job_add2 (ctx, + eh, + orh->post_ctx.headers, + &handle_refund_finished, + orh); + if (NULL == orh->job) + { + GNUNET_free (orh->url); + GNUNET_free (orh); + return NULL; + } + return orh; +} + + +/** + * Cancel a (public) POST /refund request. + * + * @param orh the refund operation to cancel + */ +void +TALER_MERCHANT_wallet_post_order_refund_cancel ( + struct TALER_MERCHANT_WalletOrderRefundHandle *orh) +{ + if (NULL != orh->job) + { + GNUNET_CURL_job_cancel (orh->job); + orh->job = NULL; + } + TALER_curl_easy_post_finished (&orh->post_ctx); + GNUNET_free (orh->url); + GNUNET_free (orh); +} + + +/* end of merchant_api_wallet_post_order_refund.c */ diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 769abd97..6fabd904 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -49,6 +49,7 @@ libtalermerchanttesting_la_SOURCES = \ testing_api_cmd_tip_pickup.c \ testing_api_cmd_wallet_get_order.c \ testing_api_cmd_wallet_get_tip.c \ + testing_api_cmd_wallet_post_orders_refund.c \ testing_api_helpers.c \ testing_api_trait_claim_nonce.c \ testing_api_trait_merchant_sig.c \ diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c index 92fb3f43..8e5903e1 100644 --- a/src/testing/test_merchant_api.c +++ b/src/testing/test_merchant_api.c @@ -681,9 +681,7 @@ run (void *cls, "create-proposal-1r", true, true, - MHD_HTTP_OK, - "refund-increase-1r", - NULL), + MHD_HTTP_OK), TALER_TESTING_cmd_merchant_order_refund ("refund-increase-1r-2", merchant_url, "refund test 2", @@ -695,10 +693,14 @@ run (void *cls, "create-proposal-1r", true, true, - MHD_HTTP_OK, - "refund-increase-1r", - "refund-increase-1r-2", - NULL), + MHD_HTTP_OK), + TALER_TESTING_cmd_wallet_order_refund ("obtain-refund-1r", + merchant_url, + "create-proposal-1r", + MHD_HTTP_OK, + "refund-increase-1r", + "refund-increase-1r-2", + NULL), TALER_TESTING_cmd_merchant_get_order ("get-order-merchant-1r", merchant_url, "create-proposal-1r", diff --git a/src/testing/testing_api_cmd_wallet_get_order.c b/src/testing/testing_api_cmd_wallet_get_order.c index b4a4ce71..aeae42a5 100644 --- a/src/testing/testing_api_cmd_wallet_get_order.c +++ b/src/testing/testing_api_cmd_wallet_get_order.c @@ -67,16 +67,6 @@ struct WalletGetOrderState * Whether the order was refunded or not. */ bool refunded; - - /** - * A NULL-terminated list of refunds associated with this order. - */ - const char **refunds; - - /** - * The length of @e refunds. - */ - unsigned int refunds_length; }; @@ -96,9 +86,6 @@ struct WalletGetOrderState * the payment * @param already_paid_order_id equivalent order that this customer * paid already, or NULL for none - * @param merchant_pub public key of the merchant - * @param num_refunds length of the @a refunds array - * @param refunds details about the refund processing */ static void wallet_get_order_cb ( @@ -108,10 +95,7 @@ wallet_get_order_cb ( enum GNUNET_GenericReturnValue refunded, struct TALER_Amount *refund_amount, const char *taler_pay_uri, - const char *already_paid_order_id, - const struct TALER_MerchantPublicKeyP *merchant_pub, - unsigned int num_refunds, - const struct TALER_MERCHANT_RefundDetail *refunds) + const char *already_paid_order_id) { /* FIXME, deeper checks should be implemented here. */ struct WalletGetOrderState *gos = cls; @@ -148,59 +132,6 @@ wallet_get_order_cb ( TALER_TESTING_interpreter_fail (gos->is); return; } - if (gos->refunds_length != num_refunds) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Order refunds count does not match\n"); - TALER_TESTING_interpreter_fail (gos->is); - return; - } - { - struct TALER_Amount refunded_total; - if (num_refunds > 0) - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (refund_amount->currency, - &refunded_total)); - for (unsigned int i = 0; i < num_refunds; ++i) - { - const struct TALER_TESTING_Command *refund_cmd; - const char *expected_amount_str; - struct TALER_Amount expected_amount; - - refund_cmd = TALER_TESTING_interpreter_lookup_command ( - gos->is, - gos->refunds[i]); - - if (GNUNET_OK != - TALER_TESTING_get_trait_string (refund_cmd, - 0, - &expected_amount_str)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not fetch refund amount\n"); - TALER_TESTING_interpreter_fail (gos->is); - return; - } - GNUNET_assert (GNUNET_OK == - TALER_string_to_amount (expected_amount_str, - &expected_amount)); - /* The most recent refunds are returned first */ - GNUNET_assert (0 <= TALER_amount_add (&refunded_total, - &refunded_total, - &refunds[num_refunds - 1 - i].refund_amount)); - if ((GNUNET_OK != - TALER_amount_cmp_currency (&expected_amount, - &refunded_total)) || - (0 != TALER_amount_cmp (&expected_amount, - &refunded_total))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Refund amounts do not match\n"); - TALER_TESTING_interpreter_fail (gos->is); - return; - } - } - } if (!paid_b) { /* FIXME: Check all of the members of `pud` */ @@ -361,8 +292,7 @@ TALER_TESTING_cmd_wallet_get_order (const char *label, const char *order_reference, bool paid, bool refunded, - unsigned int http_status, - ...) + unsigned int http_status) { struct WalletGetOrderState *gos; @@ -372,21 +302,6 @@ TALER_TESTING_cmd_wallet_get_order (const char *label, gos->http_status = http_status; gos->paid = paid; gos->refunded = refunded; - gos->refunds_length = 0; - if (refunded) - { - const char *clabel; - va_list ap; - - va_start (ap, http_status); - while (NULL != (clabel = va_arg (ap, const char *))) - { - GNUNET_array_append (gos->refunds, - gos->refunds_length, - clabel); - } - va_end (ap); - } { struct TALER_TESTING_Command cmd = { .cls = gos, diff --git a/src/testing/testing_api_cmd_wallet_post_orders_refund.c b/src/testing/testing_api_cmd_wallet_post_orders_refund.c new file mode 100644 index 00000000..0df41eda --- /dev/null +++ b/src/testing/testing_api_cmd_wallet_post_orders_refund.c @@ -0,0 +1,316 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 3, 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 General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, see + +*/ +/** + * @file lib/testing_api_cmd_wallet_post_orders_refund.c + * @brief command to test refunds. + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler_merchant_service.h" +#include "taler_merchant_testing_lib.h" + + +/** + * State for an "obtain refunds" CMD. + */ +struct WalletRefundState +{ + /** + * Operation handle for a (public) POST /orders/$ID/refund request. + */ + struct TALER_MERCHANT_WalletOrderRefundHandle *orh; + + /** + * Base URL of the merchant serving the request. + */ + const char *merchant_url; + + /** + * Interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * Expected HTTP response code. + */ + unsigned int http_code; + + /** + * Label of the command that created the order we want to obtain refunds for. + */ + const char *proposal_reference; + + /** + * A list of refunds associated with this order. + */ + const char **refunds; + + /** + * The length of @e refunds. + */ + unsigned int refunds_length; +}; + + +/** + * Process POST /refund (increase) response; just checking + * if the HTTP response code is the one expected. + * + * @param cls closure + * @param hr HTTP response + */ +static void +refund_cb ( + void *cls, + const struct TALER_MERCHANT_HttpResponse *hr, + const struct TALER_Amount *refund_amount, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct TALER_MERCHANT_RefundDetail refunds[], + unsigned int refunds_length) +{ + struct WalletRefundState *wrs = cls; + + wrs->orh = NULL; + if (wrs->http_code != hr->http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected status %u, got %u(%d) for refund increase\n", + wrs->http_code, + hr->http_status, + (int) hr->ec); + TALER_TESTING_FAIL (wrs->is); + } + switch (hr->http_status) + { + case MHD_HTTP_OK: + { + struct TALER_Amount refunded_total; + if (refunds_length > 0) + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (refunds[0].refund_amount.currency, + &refunded_total)); + for (unsigned int i = 0; i < refunds_length; ++i) + { + const struct TALER_TESTING_Command *refund_cmd; + const char *expected_amount_str; + struct TALER_Amount expected_amount; + + refund_cmd = TALER_TESTING_interpreter_lookup_command ( + wrs->is, + wrs->refunds[i]); + + if (GNUNET_OK != + TALER_TESTING_get_trait_string (refund_cmd, + 0, + &expected_amount_str)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not fetch refund amount\n"); + TALER_TESTING_interpreter_fail (wrs->is); + return; + } + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (expected_amount_str, + &expected_amount)); + /* The most recent refunds are returned first */ + GNUNET_assert (0 <= TALER_amount_add (&refunded_total, + &refunded_total, + &refunds[refunds_length - 1 - i].refund_amount)); + if ((GNUNET_OK != + TALER_amount_cmp_currency (&expected_amount, + &refunded_total)) || + (0 != TALER_amount_cmp (&expected_amount, + &refunded_total))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Refund amounts do not match\n"); + TALER_TESTING_interpreter_fail (wrs->is); + return; + } + } + } + break; + default: + + break; + } + TALER_TESTING_interpreter_next (wrs->is); +} + + +/** + * Run the "refund increase" CMD. + * + * @param cls closure. + * @param cmd command currently being run. + * @param is the interpreter state. + */ +static void +obtain_refunds_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct WalletRefundState *wrs = cls; + const struct TALER_TESTING_Command *proposal_cmd = + TALER_TESTING_interpreter_lookup_command (is, + wrs->proposal_reference); + const struct GNUNET_HashCode *h_contract_terms; + const char *order_id; + + if (NULL == proposal_cmd) + TALER_TESTING_FAIL (is); + if (GNUNET_OK != + TALER_TESTING_get_trait_h_contract_terms (proposal_cmd, + 0, + &h_contract_terms)) + TALER_TESTING_FAIL (is); + + { + const json_t *contract_terms; + const char *error_name; + unsigned int error_line; + + if (GNUNET_OK != + TALER_TESTING_get_trait_contract_terms (proposal_cmd, + 0, + &contract_terms)) + TALER_TESTING_FAIL (is); + { + /* Get information that needs to be put verbatim in the + * deposit permission */ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("order_id", + &order_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (contract_terms, + spec, + &error_name, + &error_line)) + { + char *js; + + js = json_dumps (contract_terms, + JSON_INDENT (1)); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Parser failed on %s:%u for input `%s'\n", + error_name, + error_line, + js); + free (js); + TALER_TESTING_FAIL (is); + } + } + } + + wrs->is = is; + wrs->orh = TALER_MERCHANT_wallet_post_order_refund ( + is->ctx, + wrs->merchant_url, + order_id, + h_contract_terms, + &refund_cb, + wrs); + if (NULL == wrs->orh) + TALER_TESTING_FAIL (is); +} + + +/** + * Free the state of a "refund increase" CMD, and + * possibly cancel a pending "refund increase" operation. + * + * @param cls closure + * @param cmd command currently being freed. + */ +static void +obtain_refunds_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct WalletRefundState *wrs = cls; + + if (NULL != wrs->orh) + { + TALER_LOG_WARNING ("Refund operation did not complete\n"); + TALER_MERCHANT_wallet_post_order_refund_cancel (wrs->orh); + } + GNUNET_array_grow (wrs->refunds, + wrs->refunds_length, + 0); + GNUNET_free (wrs); +} + + +/** + * Define a "refund order" CMD. + * + * @param label command label. + * @param merchant_url base URL of the backend serving the + * "refund increase" request. + * @param order_ref order id of the contract to refund. + * @param http_code expected HTTP response code. + * @param ... NULL-terminated list of labels (const char *) of + * refunds (commands) we expect to be aggregated in the transfer + * (assuming @a http_code is #MHD_HTTP_OK). If @e refunded is false, + * this parameter is ignored. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_wallet_order_refund (const char *label, + const char *merchant_url, + const char *order_ref, + unsigned int http_code, + ...) +{ + struct WalletRefundState *wrs; + + wrs = GNUNET_new (struct WalletRefundState); + wrs->merchant_url = merchant_url; + wrs->proposal_reference = order_ref; + wrs->http_code = http_code; + wrs->refunds_length = 0; + { + const char *clabel; + va_list ap; + + va_start (ap, http_code); + while (NULL != (clabel = va_arg (ap, const char *))) + { + GNUNET_array_append (wrs->refunds, + wrs->refunds_length, + clabel); + } + va_end (ap); + } + { + struct TALER_TESTING_Command cmd = { + .cls = wrs, + .label = label, + .run = &obtain_refunds_run, + .cleanup = &obtain_refunds_cleanup + }; + + return cmd; + } +} -- cgit v1.2.3