/*
This file is part of TALER
Copyright (C) 2020-2023 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 merchant_api_common.c
* @brief Implementation of common logic for libtalermerchant
* @author Christian Grothoff
* @author Priscilla Huang
*/
#include "platform.h"
#include
#include "taler_merchant_service.h"
#include "merchant_api_common.h"
#include
#include
void
TALER_MERCHANT_parse_error_details_ (const json_t *response,
unsigned int http_status,
struct TALER_MERCHANT_HttpResponse *hr)
{
const json_t *jc;
memset (hr, 0, sizeof (*hr));
hr->reply = response;
hr->http_status = http_status;
if (NULL == response)
{
hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE;
return;
}
hr->ec = TALER_JSON_get_error_code (response);
hr->hint = TALER_JSON_get_error_hint (response);
/* handle 'exchange_http_status' */
jc = json_object_get (response,
"exchange_http_status");
/* The caller already knows that the JSON represents an error,
so we are dealing with a missing error code here. */
if (NULL == jc)
return; /* no need to bother with exchange_code/hint if we had no status */
if (! json_is_integer (jc))
{
GNUNET_break_op (0);
return;
}
hr->exchange_http_status = (unsigned int) json_integer_value (jc);
/* handle 'exchange_reply' */
jc = json_object_get (response,
"exchange_reply");
if (! json_is_object (jc))
{
GNUNET_break_op (0);
}
else
{
hr->exchange_reply = jc;
}
/* handle 'exchange_code' */
jc = json_object_get (response,
"exchange_code");
/* The caller already knows that the JSON represents an error,
so we are dealing with a missing error code here. */
if (NULL == jc)
return; /* no need to bother with exchange-hint if we had no code */
if (! json_is_integer (jc))
{
GNUNET_break_op (0);
hr->exchange_code = TALER_EC_INVALID;
}
else
{
hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc);
}
/* handle 'exchange-hint' */
jc = json_object_get (response,
"exchange-hint");
/* The caller already knows that the JSON represents an error,
so we are dealing with a missing error code here. */
if (NULL == jc)
return;
if (! json_is_string (jc))
{
GNUNET_break_op (0);
}
else
{
hr->exchange_hint = json_string_value (jc);
}
}
enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
struct TALER_MERCHANT_PayUriData *parse_data)
{
char *cp = GNUNET_strdup (pay_uri);
struct GNUNET_Uri u;
if (0 !=
GNUNET_uri_parse (&u,
cp))
{
GNUNET_free (cp);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if ((0 != strcasecmp ("taler",
u.scheme)) &&
(0 != strcasecmp ("taler+http",
u.scheme)))
{
fprintf (stderr,
"Bad schema %s\n",
u.scheme);
GNUNET_free (cp);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
parse_data->use_http = (0 == strcasecmp ("taler+http",
u.scheme));
if (0 != strcasecmp ("pay",
u.host))
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
{
char *order_id;
char *mpp;
char *session_id = strrchr (u.path,
'/');
struct TALER_ClaimTokenP *claim_token = NULL;
if (NULL == session_id)
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
*session_id = '\0';
++session_id;
order_id = strrchr (u.path,
'/');
if (NULL == order_id)
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
*order_id = '\0';
++order_id;
mpp = strchr (u.path,
'/');
if (NULL != mpp)
{
*mpp = '\0';
++mpp;
}
{
char *ct_str = u.query;
char *ct_data;
if (NULL != ct_str)
{
ct_data = strchr (u.query,
'=');
if (NULL == ct_data)
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
*ct_data = '\0';
++ct_data;
claim_token = GNUNET_new (struct TALER_ClaimTokenP);
if ( (0 != strcmp ("c",
u.query)) ||
(GNUNET_OK !=
GNUNET_STRINGS_string_to_data (ct_data,
strlen (ct_data),
claim_token,
sizeof (*claim_token))) )
{
GNUNET_break_op (0);
GNUNET_free (claim_token);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
}
}
parse_data->merchant_prefix_path
= (NULL == mpp)
? NULL
: GNUNET_strdup (mpp);
parse_data->merchant_host = GNUNET_strdup (u.path);
parse_data->order_id = GNUNET_strdup (order_id);
parse_data->session_id
= (0 < strlen (session_id))
? GNUNET_strdup (session_id)
: NULL;
parse_data->claim_token = claim_token;
parse_data->ssid
= (NULL == u.fragment)
? NULL
: GNUNET_strdup (u.fragment);
}
GNUNET_free (cp);
return GNUNET_OK;
}
void
TALER_MERCHANT_parse_pay_uri_free (
struct TALER_MERCHANT_PayUriData *parse_data)
{
GNUNET_free (parse_data->merchant_host);
GNUNET_free (parse_data->merchant_prefix_path);
GNUNET_free (parse_data->order_id);
GNUNET_free (parse_data->session_id);
GNUNET_free (parse_data->claim_token);
GNUNET_free (parse_data->ssid);
}
enum GNUNET_GenericReturnValue
TALER_MERCHANT_parse_refund_uri (
const char *refund_uri,
struct TALER_MERCHANT_RefundUriData *parse_data)
{
char *cp = GNUNET_strdup (refund_uri);
struct GNUNET_Uri u;
if (0 !=
GNUNET_uri_parse (&u,
cp))
{
GNUNET_free (cp);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
if ((0 != strcasecmp ("taler",
u.scheme)) &&
(0 != strcasecmp ("taler+http",
u.scheme)))
{
GNUNET_free (cp);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
parse_data->use_http = (0 == strcasecmp ("taler+http",
u.scheme));
if (0 != strcasecmp ("refund",
u.host))
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
{
char *order_id;
char *last_seg = strrchr (u.path,
'/');
if (NULL == last_seg)
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
*last_seg = '\0';
++last_seg;
order_id = strrchr (u.path,
'/');
if (NULL == order_id)
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
*order_id = '\0';
++order_id;
if (0 != strlen (last_seg))
{
GNUNET_break_op (0);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
{
char *mpp;
mpp = strchr (u.path,
'/');
if (NULL != mpp)
{
*mpp = '\0';
++mpp;
}
parse_data->merchant_prefix_path
= (NULL == mpp)
? NULL
: GNUNET_strdup (mpp);
}
parse_data->merchant_host = GNUNET_strdup (u.path);
parse_data->order_id = GNUNET_strdup (order_id);
parse_data->ssid
= (NULL == u.fragment)
? NULL
: GNUNET_strdup (u.fragment);
}
GNUNET_free (cp);
return GNUNET_OK;
}
void
TALER_MERCHANT_parse_refund_uri_free (
struct TALER_MERCHANT_RefundUriData *parse_data)
{
GNUNET_free (parse_data->merchant_host);
GNUNET_free (parse_data->merchant_prefix_path);
GNUNET_free (parse_data->order_id);
GNUNET_free (parse_data->ssid);
}
void
TALER_MERCHANT_handle_order_creation_response_ (
TALER_MERCHANT_PostOrdersCallback cb,
void *cb_cls,
long response_code,
const json_t *json)
{
struct TALER_MERCHANT_PostOrdersReply por = {
.hr.http_status = (unsigned int) response_code,
.hr.reply = json
};
struct TALER_ClaimTokenP token;
switch (response_code)
{
case 0:
por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
bool no_token;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("order_id",
&por.details.ok.order_id),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto ("token",
&token),
&no_token),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
por.hr.http_status = 0;
por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
else
{
if (! no_token)
por.details.ok.token = &token;
}
}
break;
case MHD_HTTP_BAD_REQUEST:
json_dumpf (json,
stderr,
JSON_INDENT (2));
por.hr.ec = TALER_JSON_get_error_code (json);
por.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_UNAUTHORIZED:
por.hr.ec = TALER_JSON_get_error_code (json);
por.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, merchant says we need to authenticate. */
break;
case MHD_HTTP_FORBIDDEN:
/* 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 */
por.hr.ec = TALER_JSON_get_error_code (json);
por.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
por.hr.ec = TALER_JSON_get_error_code (json);
por.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_CONFLICT:
por.hr.ec = TALER_JSON_get_error_code (json);
por.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_GONE:
/* The quantity of some product requested was not available. */
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string (
"product_id",
&por.details.gone.product_id),
GNUNET_JSON_spec_uint64 (
"requested_quantity",
&por.details.gone.requested_quantity),
GNUNET_JSON_spec_uint64 (
"available_quantity",
&por.details.gone.available_quantity),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp (
"restock_expected",
&por.details.gone.restock_expected),
NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
por.hr.http_status = 0;
por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
break;
}
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry,
but this API leaves this to the application */
por.hr.ec = TALER_JSON_get_error_code (json);
por.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
por.hr.ec = TALER_JSON_get_error_code (json);
por.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
(int) por.hr.ec);
GNUNET_break_op (0);
break;
} // end of switch
cb (cb_cls,
&por);
}