aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/taler-merchant-httpd_get-orders-ID.c41
-rw-r--r--src/include/taler_merchant_service.h144
-rw-r--r--src/include/taler_merchant_testing_lib.h29
-rw-r--r--src/lib/Makefile.am3
-rw-r--r--src/lib/merchant_api_wallet_get_order.c182
-rw-r--r--src/lib/merchant_api_wallet_post_order_refund.c448
-rw-r--r--src/testing/Makefile.am1
-rw-r--r--src/testing/test_merchant_api.c16
-rw-r--r--src/testing/testing_api_cmd_wallet_get_order.c89
-rw-r--r--src/testing/testing_api_cmd_wallet_post_orders_refund.c316
10 files changed, 950 insertions, 319 deletions
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
@@ -1456,48 +1456,6 @@ 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
*
* @param cls closure
@@ -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);
/**
@@ -820,6 +815,28 @@ TALER_TESTING_cmd_merchant_order_refund (const char *label,
/**
+ * 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.
*
* @param label command label.
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; i<refund_len; i++)
- {
- struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
- const json_t *jrefund = json_array_get (refunds,
- i);
- const char *refund_status_type;
- uint32_t exchange_status;
- int ret;
- struct GNUNET_JSON_Specification espec[] = {
- GNUNET_JSON_spec_uint32 ("exchange_status",
- &exchange_status),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jrefund,
- espec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- cb_failure (owgh,
- TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
- json);
- TALER_MERCHANT_wallet_order_get_cancel (owgh);
- return;
- }
-
- if (MHD_HTTP_OK == exchange_status)
- {
- struct GNUNET_JSON_Specification rspec[] = {
- GNUNET_JSON_spec_string ("type",
- &refund_status_type),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rd->exchange_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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <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_curl_lib.h>
+
+
+/**
+ * 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; i<refund_len; i++)
+ {
+ struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
+ const json_t *jrefund = json_array_get (refunds,
+ i);
+ const char *refund_status_type;
+ uint32_t exchange_status;
+ int ret;
+ struct GNUNET_JSON_Specification espec[] = {
+ GNUNET_JSON_spec_uint32 ("exchange_status",
+ &exchange_status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jrefund,
+ espec,
+ 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 (MHD_HTTP_OK == exchange_status)
+ {
+ struct GNUNET_JSON_Specification rspec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &refund_status_type),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rd->exchange_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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#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;
+ }
+}