/*
This file is part of TALER
Copyright (C) 2018, 2019, 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_merchant_get_order.c
* @brief Implementation of the GET /private/orders/$ORDER request
* @author Christian Grothoff
* @author Marcello Stanisci
* @author Florian Dold
*/
#include "platform.h"
#include
#include
#include /* just for HTTP status codes */
#include
#include
#include "taler_merchant_service.h"
#include
#include
/**
* @brief A GET /private/orders/$ORDER handle
*/
struct TALER_MERCHANT_OrderMerchantGetHandle
{
/**
* The url for this request.
*/
char *url;
/**
* Handle for the request.
*/
struct GNUNET_CURL_Job *job;
/**
* Function to call with the result.
*/
TALER_MERCHANT_OrderMerchantGetCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Reference to the execution context.
*/
struct GNUNET_CURL_Context *ctx;
};
/**
* Function called when we're done processing the GET /private/orders/$ORDER
* request and we got an HTTP status of OK and the order was unpaid. Parse
* the response and call the callback.
*
* @param omgh handle for the request
* @param[in,out] hr HTTP response we got
*/
static void
handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
struct TALER_MERCHANT_HttpResponse *hr)
{
const char *taler_pay_uri
= json_string_value (json_object_get (hr->reply,
"taler_pay_uri"));
const char *already_paid_order_id
= json_string_value (json_object_get (hr->reply,
"already_paid_order_id"));
if (NULL == taler_pay_uri)
{
GNUNET_break_op (0);
hr->http_status = 0;
hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
hr,
NULL);
return;
}
{
struct TALER_MERCHANT_OrderStatusResponse osr = {
.paid = false,
.details.unpaid.taler_pay_uri = taler_pay_uri,
.details.unpaid.already_paid_order_id = already_paid_order_id
};
omgh->cb (omgh->cb_cls,
hr,
&osr);
}
}
/**
* Function called when we're done processing the GET /private/orders/$ORDER
* request and we got an HTTP status of OK and the order was paid. Parse
* the response and call the callback.
*
* @param omgh handle for the request
* @param[in,out] hr HTTP response we got
*/
static void
handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
struct TALER_MERCHANT_HttpResponse *hr)
{
uint32_t ec32;
uint32_t hc32;
json_t *wire_details;
json_t *wire_reports;
json_t *refund_details;
struct TALER_MERCHANT_OrderStatusResponse osr = {
.paid = true
};
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_bool ("refunded",
&osr.details.paid.refunded),
GNUNET_JSON_spec_bool ("wired",
&osr.details.paid.wired),
TALER_JSON_spec_amount ("deposit_total",
&osr.details.paid.deposit_total),
GNUNET_JSON_spec_uint32 ("exchange_ec",
&ec32),
GNUNET_JSON_spec_uint32 ("exchange_hc",
&hc32),
TALER_JSON_spec_amount ("refund_amount",
&osr.details.paid.refund_amount),
GNUNET_JSON_spec_json ("contract_terms",
(json_t **) &osr.details.paid.contract_terms),
GNUNET_JSON_spec_json ("wire_details",
&wire_details),
GNUNET_JSON_spec_json ("wire_reports",
&wire_reports),
GNUNET_JSON_spec_json ("refund_details",
&refund_details),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (hr->reply,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
hr->http_status = 0;
hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
hr,
NULL);
return;
}
if (! (json_is_array (wire_details) &&
json_is_array (wire_reports) &&
json_is_array (refund_details) &&
json_is_object (osr.details.paid.contract_terms)) )
{
GNUNET_break_op (0);
hr->http_status = 0;
hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
hr,
NULL);
GNUNET_JSON_parse_free (spec);
return;
}
osr.details.paid.exchange_ec = (enum TALER_ErrorCode) ec32;
osr.details.paid.exchange_hc = (unsigned int) hc32;
{
unsigned int wts_len = json_array_size (wire_details);
unsigned int wrs_len = json_array_size (wire_reports);
unsigned int ref_len = json_array_size (refund_details);
struct TALER_MERCHANT_WireTransfer wts[wts_len];
struct TALER_MERCHANT_WireReport wrs[wrs_len];
struct TALER_MERCHANT_RefundOrderDetail ref[ref_len];
for (unsigned int i = 0; iexchange_url),
GNUNET_JSON_spec_fixed_auto ("wtid",
&wt->wtid),
TALER_JSON_spec_absolute_time ("execution_time",
&wt->execution_time),
TALER_JSON_spec_amount ("amount",
&wt->total_amount),
GNUNET_JSON_spec_bool ("confirmed",
&wt->confirmed),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (w,
ispec,
NULL, NULL))
{
GNUNET_break_op (0);
hr->http_status = 0;
hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
hr,
NULL);
GNUNET_JSON_parse_free (spec);
return;
}
}
for (unsigned int i = 0; ihint),
GNUNET_JSON_spec_fixed_auto ("coin_pub",
&wr->coin_pub),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (w,
ispec,
NULL, NULL))
{
GNUNET_break_op (0);
hr->http_status = 0;
hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
hr,
NULL);
GNUNET_JSON_parse_free (spec);
return;
}
wr->code = (enum TALER_ErrorCode) c32;
wr->hr.ec = (enum TALER_ErrorCode) eec32;
wr->hr.http_status = (unsigned int) ehs32;
}
for (unsigned int i = 0; irefund_amount),
GNUNET_JSON_spec_string ("reason",
&ro->reason),
TALER_JSON_spec_absolute_time ("timestamp",
&ro->refund_time),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (w,
ispec,
NULL, NULL))
{
GNUNET_break_op (0);
hr->http_status = 0;
hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
omgh->cb (omgh->cb_cls,
hr,
NULL);
GNUNET_JSON_parse_free (spec);
return;
}
}
osr.details.paid.wts = wts;
osr.details.paid.wts_len = wts_len;
osr.details.paid.wrs = wrs;
osr.details.paid.wrs_len = wrs_len;
osr.details.paid.refunds = ref;
osr.details.paid.refunds_len = ref_len;
omgh->cb (omgh->cb_cls,
hr,
&osr);
}
GNUNET_JSON_parse_free (spec);
}
/**
* Function called when we're done processing the GET /private/orders/$ORDER
* request.
*
* @param cls the `struct TALER_MERCHANT_OrderMerchantGetHandle`
* @param response_code HTTP response code, 0 on error
* @param json response body, should be NULL
*/
static void
handle_merchant_order_get_finished (void *cls,
long response_code,
const void *response)
{
struct TALER_MERCHANT_OrderMerchantGetHandle *omgh = cls;
const json_t *json = response;
struct TALER_MERCHANT_HttpResponse hr = {
.http_status = (unsigned int) response_code,
.reply = json
};
omgh->job = NULL;
switch (response_code)
{
case MHD_HTTP_NOT_FOUND:
hr.ec = TALER_JSON_get_error_code (json);
hr.hint = TALER_JSON_get_error_hint (json);
omgh->cb (omgh->cb_cls,
&hr,
NULL);
TALER_MERCHANT_merchant_order_get_cancel (omgh);
return;
case MHD_HTTP_OK:
/* see below */
break;
default:
hr.ec = TALER_JSON_get_error_code (json);
hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Polling payment failed with HTTP status code %u/%d\n",
(unsigned int) response_code,
(int) hr.ec);
GNUNET_break_op (0);
omgh->cb (omgh->cb_cls,
&hr,
NULL);
TALER_MERCHANT_merchant_order_get_cancel (omgh);
return;
}
/* HTTP OK */
if (! json_boolean_value (json_object_get (json, "paid")))
handle_unpaid (omgh,
&hr);
else
handle_paid (omgh,
&hr);
}
/**
* Checks the status of a payment. Issue a GET /private/orders/$ID request to
* the backend.
*
* @param ctx execution context
* @param backend_url base URL of the merchant backend
* @param order_id order id to identify the payment
* @param session_id sesion id for the payment (or NULL if the check is not
* bound to a session)
* @param transfer if true, obtain the wire transfer status from the exhcange.
* Otherwise, the wire transfer status MAY be returned if it is available.
* @param timeout timeout to use in long polling (how long may the server wait to reply
* before generating an unpaid response). Note that this is just provided to
* the server, we as client will block until the response comes back or until
* #TALER_MERCHANT_order_get_cancel() is called.
* @param cb callback which will work the response gotten from the backend
* @param cb_cls closure to pass to @a cb
* @return handle for this operation, NULL upon errors
*/
struct TALER_MERCHANT_OrderMerchantGetHandle *
TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *order_id,
const char *session_id,
bool transfer,
struct GNUNET_TIME_Relative timeout,
TALER_MERCHANT_OrderMerchantGetCallback cb,
void *cb_cls)
{
struct TALER_MERCHANT_OrderMerchantGetHandle *omgh;
unsigned long long tms;
long tlong;
GNUNET_assert (NULL != backend_url);
GNUNET_assert (NULL != order_id);
tms = (unsigned long long) (timeout.rel_value_us
/ GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
/* set curl timeout to *our* long poll timeout plus one minute
(for network latency and processing delays) */
tlong = (long) (GNUNET_TIME_relative_add (timeout,
GNUNET_TIME_UNIT_MINUTES).
rel_value_us
/ GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle);
omgh->ctx = ctx;
omgh->cb = cb;
omgh->cb_cls = cb_cls;
{
char *path;
char timeout_ms[32];
GNUNET_snprintf (timeout_ms,
sizeof (timeout_ms),
"%llu",
tms);
GNUNET_asprintf (&path,
"private/orders/%s",
order_id);
omgh->url = TALER_url_join (backend_url,
path,
"session_id", session_id,
"transfer", transfer ? "YES" : "NO",
(0 != tms) ? "timeout_ms" : NULL,
timeout_ms,
NULL);
}
if (NULL == omgh->url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not construct request URL.\n");
GNUNET_free (omgh);
return NULL;
}
{
CURL *eh;
eh = curl_easy_init ();
if (NULL == eh)
{
GNUNET_break (0);
GNUNET_free (omgh->url);
GNUNET_free (omgh);
return NULL;
}
if (CURLE_OK != curl_easy_setopt (eh,
CURLOPT_URL,
omgh->url))
{
GNUNET_break (0);
curl_easy_cleanup (eh);
GNUNET_free (omgh->url);
GNUNET_free (omgh);
return NULL;
}
if (CURLE_OK != curl_easy_setopt (eh,
CURLOPT_TIMEOUT_MS,
tlong))
{
GNUNET_break (0);
curl_easy_cleanup (eh);
GNUNET_free (omgh->url);
GNUNET_free (omgh);
return NULL;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Getting order status from %s\n",
omgh->url);
if (NULL == (omgh->job =
GNUNET_CURL_job_add (ctx,
eh,
GNUNET_YES,
&handle_merchant_order_get_finished,
omgh)))
{
GNUNET_break (0);
GNUNET_free (omgh->url);
GNUNET_free (omgh);
return NULL;
}
}
return omgh;
}
/**
* Cancel a GET /private/orders/$ORDER request.
*
* @param omgh handle to the request to be canceled
*/
void
TALER_MERCHANT_merchant_order_get_cancel (
struct TALER_MERCHANT_OrderMerchantGetHandle *omgh)
{
if (NULL != omgh->job)
{
GNUNET_CURL_job_cancel (omgh->job);
omgh->job = NULL;
}
GNUNET_free (omgh->url);
GNUNET_free (omgh);
}
/* end of merchant_api_merchant_get_order.c */