/*
  This file is part of TALER
  (C) 2014--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 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 taler-merchant-httpd_helper.c
 * @brief shared logic for various handlers
 * @author Christian Grothoff
 */
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <taler/taler_json_lib.h>
#include "taler-merchant-httpd_helper.h"
#include <taler/taler_templating_lib.h>
#include <taler/taler_dbevents.h>


enum GNUNET_GenericReturnValue
TMH_parse_fractional_string (const char *value,
                             int64_t *integer_part,
                             uint32_t *fractional_part)
{
  const char *ptr;
  uint64_t integer = 0;
  uint32_t frac = 0;
  unsigned int digits = 0;

  GNUNET_assert (NULL != integer_part);
  GNUNET_assert (NULL != fractional_part);

  if (NULL == value)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  ptr = value;
  if ('\0' == *ptr)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if ('-' == *ptr)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if (! isdigit ((unsigned char) *ptr))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  while (isdigit ((unsigned char) *ptr))
  {
    unsigned int digit = (unsigned int) (*ptr - '0');

    if (integer > (UINT64_MAX - digit) / 10)
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    integer = integer * 10 + digit;
    ptr++;
  }
  if ('.' == *ptr)
  {
    ptr++;
    if ('\0' == *ptr)
    {
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    while (isdigit ((unsigned char) *ptr))
    {
      unsigned int digit = (unsigned int) (*ptr - '0');

      if (digits >= MERCHANT_UNIT_FRAC_MAX_DIGITS)
      {
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
      frac = (uint32_t) (frac * 10 + digit);
      digits++;
      ptr++;
    }
    while (digits < MERCHANT_UNIT_FRAC_MAX_DIGITS)
    {
      frac *= 10;
      digits++;
    }
  }
  if ('\0' != *ptr)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  if (integer > (uint64_t) INT64_MAX)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  *integer_part = integer;
  *fractional_part = frac;
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TMH_process_quantity_inputs (enum TMH_ValueKind kind,
                             bool allow_fractional,
                             bool int_missing,
                             int64_t int_raw,
                             bool str_missing,
                             const char *str_raw,
                             uint64_t *int_out,
                             uint32_t *frac_out,
                             const char **error_param)
{
  static char errbuf[128];
  int64_t parsed_int = 0;
  uint32_t parsed_frac = 0;
  const char *int_field = (TMH_VK_STOCK == kind)
                          ? "total_stock"
                          : "quantity";
  const char *str_field = (TMH_VK_STOCK == kind)
                          ? "unit_total_stock"
                          : "unit_quantity";

  *error_param = NULL;

  if (int_missing && str_missing)
  {
    GNUNET_snprintf (errbuf,
                     sizeof (errbuf),
                     "missing %s and %s",
                     int_field,
                     str_field);
    *error_param = errbuf;
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  if (! str_missing)
  {
    if ( (TMH_VK_STOCK == kind) &&
         (0 == strcmp ("-1",
                       str_raw)) )
    {
      parsed_int = -1;
      parsed_frac = 0;
    }
    else
    {
      if (GNUNET_OK !=
          TMH_parse_fractional_string (str_raw,
                                       &parsed_int,
                                       &parsed_frac))
      {
        GNUNET_snprintf (errbuf,
                         sizeof (errbuf),
                         "malformed %s",
                         str_field);
        *error_param = errbuf;
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
    }
  }

  if ( (! int_missing) && (! str_missing) )
  {
    if ( (parsed_int != int_raw) || (0 != parsed_frac) )
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "%s/%s mismatch",
                       int_field,
                       str_field);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
  }
  else if (int_missing)
  {
    int_raw = parsed_int;
  }

  if ( (TMH_VK_STOCK == kind) && (-1 == int_raw) )
  {
    if ( (! str_missing) && (0 != parsed_frac) )
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "fractional part forbidden with %s='-1'",
                       str_field);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    *int_out = INT64_MAX;
    *frac_out = INT32_MAX;
    return GNUNET_OK;
  }

  if (int_raw < 0)
  {
    GNUNET_snprintf (errbuf,
                     sizeof (errbuf),
                     "%s must be non-negative",
                     int_field);
    *error_param = errbuf;
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  if (! allow_fractional)
  {
    if ( (! str_missing) && (0 != parsed_frac) )
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "fractional part not allowed for %s",
                       str_field);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
    parsed_frac = 0;
  }
  else if (! str_missing)
  {
    if (parsed_frac >= MERCHANT_UNIT_FRAC_BASE)
    {
      GNUNET_snprintf (errbuf,
                       sizeof (errbuf),
                       "%s fractional part exceeds base %u",
                       str_field,
                       MERCHANT_UNIT_FRAC_BASE);
      *error_param = errbuf;
      GNUNET_break_op (0);
      return GNUNET_SYSERR;
    }
  }

  *int_out = (uint64_t) int_raw;
  *frac_out = parsed_frac;
  return GNUNET_OK;
}


void
TMH_format_fractional_string (enum TMH_ValueKind kind,
                              uint64_t integer,
                              uint32_t fractional,
                              size_t buffer_length,
                              char buffer[static buffer_length])
{
  GNUNET_assert (0 < buffer_length);

  if ( (TMH_VK_STOCK == kind) &&
       (INT64_MAX == (int64_t) integer) &&
       (INT32_MAX == (int32_t) fractional) )
  {
    GNUNET_snprintf (buffer,
                     buffer_length,
                     "-1");
    return;
  }

  GNUNET_assert ( (TMH_VK_QUANTITY != kind) ||
                  ((INT64_MAX != (int64_t) integer) &&
                   (INT32_MAX != (int32_t) fractional)) );
  GNUNET_assert (fractional < MERCHANT_UNIT_FRAC_BASE);

  if (0 == fractional)
  {
    GNUNET_snprintf (buffer,
                     buffer_length,
                     "%lu",
                     integer);
    return;
  }
  {
    char frac_buf[MERCHANT_UNIT_FRAC_MAX_DIGITS + 1];
    size_t idx;

    GNUNET_snprintf (frac_buf,
                     sizeof (frac_buf),
                     "%0*u",
                     MERCHANT_UNIT_FRAC_MAX_DIGITS,
                     (unsigned int) fractional);
    for (idx = strlen (frac_buf); idx > 0; idx--)
    {
      if ('0' != frac_buf[idx - 1])
        break;
      frac_buf[idx - 1] = '\0';
    }
    GNUNET_snprintf (buffer,
                     buffer_length,
                     "%lu.%s",
                     integer,
                     frac_buf);
  }
}


void
TMH_quantity_defaults_from_unit (const struct TMH_MerchantInstance *mi,
                                 const char *unit,
                                 bool *allow_fractional,
                                 uint32_t *precision_level)
{
  GNUNET_assert (NULL != allow_fractional);
  GNUNET_assert (NULL != precision_level);
  if (GNUNET_OK !=
      TMH_unit_defaults_for_instance (mi,
                                      unit,
                                      allow_fractional,
                                      precision_level))
  {
    *allow_fractional = false;
    *precision_level = 0;
  }
}


enum GNUNET_GenericReturnValue
TMH_unit_defaults_for_instance (const struct TMH_MerchantInstance *mi,
                                const char *unit,
                                bool *allow_fractional,
                                uint32_t *precision_level)
{
  struct TALER_MERCHANTDB_UnitDetails ud = { 0 };
  enum GNUNET_DB_QueryStatus qs;
  bool allow = false;
  uint32_t precision = 0;

  GNUNET_assert (NULL != allow_fractional);
  GNUNET_assert (NULL != precision_level);

  qs = TMH_db->select_unit (TMH_db->cls,
                            mi->settings.id,
                            unit,
                            &ud);
  switch (qs)
  {
  case GNUNET_DB_STATUS_HARD_ERROR:
  case GNUNET_DB_STATUS_SOFT_ERROR:
    TALER_MERCHANTDB_unit_details_free (&ud);
    return GNUNET_SYSERR;
  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    allow = ud.unit_allow_fraction;
    precision = ud.unit_precision_level;
    break;
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    break;
  default:
    GNUNET_break (0);
    TALER_MERCHANTDB_unit_details_free (&ud);
    return GNUNET_SYSERR;
  }
  TALER_MERCHANTDB_unit_details_free (&ud);

  /* This is definitely not supposed to happen
     combination of allow -> false, and precision > 0
     yet let's fix it */
  if (! allow)
  {
    GNUNET_break (0);
    precision = 0;
  }
  *allow_fractional = allow;
  *precision_level = precision;
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TMH_cmp_wire_account (
  const json_t *account,
  const struct TMH_WireMethod *wm)
{
  const char *credit_facade_url = NULL;
  const json_t *credit_facade_credentials = NULL;
  struct TALER_FullPayto uri;
  struct GNUNET_JSON_Specification ispec[] = {
    TALER_JSON_spec_full_payto_uri ("payto_uri",
                                    &uri),
    GNUNET_JSON_spec_mark_optional (
      TALER_JSON_spec_web_url ("credit_facade_url",
                               &credit_facade_url),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("credit_facade_credentials",
                                     &credit_facade_credentials),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  enum GNUNET_GenericReturnValue res;
  const char *ename;
  unsigned int eline;

  res = GNUNET_JSON_parse (account,
                           ispec,
                           &ename,
                           &eline);
  if (GNUNET_OK != res)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to parse account spec: %s (%u)\n",
                ename,
                eline);
    return GNUNET_SYSERR;
  }
  if (0 !=
      TALER_full_payto_cmp (wm->payto_uri,
                            uri))
  {
    return GNUNET_SYSERR;
  }
  if ( (NULL == credit_facade_url) !=
       (NULL == wm->credit_facade_url) ||
       (NULL == credit_facade_credentials) !=
       (NULL == wm->credit_facade_credentials) )
  {
    return GNUNET_NO;
  }
  if ( (NULL != credit_facade_url) &&
       (0 != strcmp (credit_facade_url,
                     wm->credit_facade_url)) )
  {
    return GNUNET_NO;
  }
  if ( (NULL != credit_facade_credentials) &&
       (0 != json_equal (credit_facade_credentials,
                         wm->credit_facade_credentials)) )
  {
    return GNUNET_NO;
  }
  return GNUNET_YES;
}


bool
TMH_accounts_array_valid (const json_t *accounts)
{
  size_t len;

  if (! json_is_array (accounts))
  {
    GNUNET_break_op (0);
    return false;
  }
  len = json_array_size (accounts);
  for (size_t i = 0; i<len; i++)
  {
    json_t *payto_uri = json_array_get (accounts,
                                        i);
    const char *credit_facade_url = NULL;
    const json_t *credit_facade_credentials = NULL;
    struct TALER_FullPayto uri;
    struct GNUNET_JSON_Specification ispec[] = {
      TALER_JSON_spec_full_payto_uri ("payto_uri",
                                      &uri),
      GNUNET_JSON_spec_mark_optional (
        TALER_JSON_spec_web_url ("credit_facade_url",
                                 &credit_facade_url),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_const ("credit_facade_credentials",
                                       &credit_facade_credentials),
        NULL),
      GNUNET_JSON_spec_end ()
    };
    enum GNUNET_GenericReturnValue res;
    const char *ename;
    unsigned int eline;

    res = GNUNET_JSON_parse (payto_uri,
                             ispec,
                             &ename,
                             &eline);
    if (GNUNET_OK != res)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Failed to parse account spec: %s (%u)\n",
                  ename,
                  eline);
      return false;
    }

    /* Test for the same payto:// URI being given twice */
    for (size_t j = 0; j<i; j++)
    {
      json_t *old_uri = json_array_get (accounts,
                                        j);
      if (0 == strcmp (uri.full_payto,
                       json_string_value (
                         json_object_get (old_uri,
                                          "payto_uri"))))
      {
        GNUNET_break_op (0);
        return false;
      }
    }
    {
      char *err;

      if (NULL !=
          (err = TALER_payto_validate (uri)))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Encountered invalid payto://-URI `%s': %s\n",
                    uri.full_payto,
                    err);
        GNUNET_free (err);
        return false;
      }
    }
    if ( (NULL == credit_facade_url) !=
         (NULL == credit_facade_credentials) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "If credit_facade_url is given, credit_facade_credentials must also be specified (violated for %s)\n",
                  uri.full_payto);
      return false;
    }
    if ( (NULL != credit_facade_url) ||
         (NULL != credit_facade_credentials) )
    {
      struct TALER_MERCHANT_BANK_AuthenticationData auth;

      if (GNUNET_OK !=
          TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
                                               credit_facade_url,
                                               &auth))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Invalid credit facade URL or credentials `%s'\n",
                    credit_facade_url);
        return false;
      }
      TALER_MERCHANT_BANK_auth_free (&auth);
    }
  } /* end for all accounts */
  return true;
}


bool
TMH_location_object_valid (const json_t *location)
{
  const char *country = NULL;
  const char *subdivision = NULL;
  const char *district = NULL;
  const char *town = NULL;
  const char *town_loc = NULL;
  const char *postcode = NULL;
  const char *street = NULL;
  const char *building = NULL;
  const char *building_no = NULL;
  const json_t *lines = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("country",
                               &country),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("country_subdivision",
                               &subdivision),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("district",
                               &district),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("town",
                               &town),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("town_location",
                               &town_loc),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("post_code",
                               &postcode),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("street",
                               &street),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("building_name",
                               &building),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("building_number",
                               &building_no),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("address_lines",
                                    &lines),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  const char *ename;
  unsigned int eline;

  if (GNUNET_OK !=
      GNUNET_JSON_parse (location,
                         spec,
                         &ename,
                         &eline))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Invalid location for field %s\n",
                ename);
    return false;
  }
  if (NULL != lines)
  {
    size_t idx;
    json_t *line;

    json_array_foreach (lines, idx, line)
    {
      if (! json_is_string (line))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Invalid address line #%u in location\n",
                    (unsigned int) idx);
        return false;
      }
    }
  }
  return true;
}


bool
TMH_products_array_valid (const json_t *products)
{
  const json_t *product;
  size_t idx;
  bool valid = true;

  if (! json_is_array (products))
  {
    GNUNET_break_op (0);
    return false;
  }
  json_array_foreach ((json_t *) products, idx, product)
  {
    const char *product_id = NULL;
    const char *description;
    uint64_t quantity = 0;
    bool quantity_missing = true;
    const char *unit_quantity = NULL;
    bool unit_quantity_missing = true;
    const char *unit = NULL;
    struct TALER_Amount price = { .value = 0 };
    const char *image_data_url = NULL;
    const json_t *taxes = NULL;
    struct GNUNET_TIME_Timestamp delivery_date = { 0 };
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("product_id",
                                 &product_id),
        NULL),
      TALER_JSON_spec_i18n_str ("description",
                                &description),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_uint64 ("quantity",
                                 &quantity),
        &quantity_missing),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("unit_quantity",
                                 &unit_quantity),
        &unit_quantity_missing),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("unit",
                                 &unit),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        TALER_JSON_spec_amount_any ("price",
                                    &price),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string ("image",
                                 &image_data_url),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_array_const ("taxes",
                                      &taxes),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_timestamp ("delivery_date",
                                    &delivery_date),
        NULL),
      GNUNET_JSON_spec_end ()
    };
    const char *ename;
    unsigned int eline;

    if (GNUNET_OK !=
        GNUNET_JSON_parse (product,
                           spec,
                           &ename,
                           &eline))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Invalid product #%u for field %s\n",
                  (unsigned int) idx,
                  ename);
      return false;
    }
    if (quantity_missing)
    {
      if (unit_quantity_missing)
      {
        GNUNET_break_op (0);
        valid = false;
      }
    }
    if ( (NULL != image_data_url) &&
         (! TMH_image_data_url_valid (image_data_url)) )
    {
      GNUNET_break_op (0);
      valid = false;
    }
    if ( (NULL != taxes) &&
         (! TMH_taxes_array_valid (taxes)) )
    {
      GNUNET_break_op (0);
      valid = false;
    }
    GNUNET_JSON_parse_free (spec);
    if (! valid)
      break;
  }

  return valid;
}


bool
TMH_image_data_url_valid (const char *image_data_url)
{
  if (0 == strcmp (image_data_url,
                   ""))
    return true;
  if (0 != strncasecmp ("data:image/",
                        image_data_url,
                        strlen ("data:image/")))
  {
    GNUNET_break_op (0);
    return false;
  }
  if (NULL == strstr (image_data_url,
                      ";base64,"))
  {
    GNUNET_break_op (0);
    return false;
  }
  if (! TALER_url_valid_charset (image_data_url))
  {
    GNUNET_break_op (0);
    return false;
  }
  return true;
}


bool
TMH_template_contract_valid (const json_t *template_contract)
{
  const char *summary;
  const char *currency;
  struct TALER_Amount amount = { .value = 0};
  uint32_t minimum_age = 0;
  struct GNUNET_TIME_Relative pay_duration = { 0 };
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("summary",
                               &summary),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("currency",
                               &currency),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      TALER_JSON_spec_amount_any ("amount",
                                  &amount),
      NULL),
    GNUNET_JSON_spec_uint32 ("minimum_age",
                             &minimum_age),
    GNUNET_JSON_spec_relative_time ("pay_duration",
                                    &pay_duration),
    GNUNET_JSON_spec_end ()
  };
  const char *ename;
  unsigned int eline;

  if (GNUNET_OK !=
      GNUNET_JSON_parse (template_contract,
                         spec,
                         &ename,
                         &eline))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Invalid template_contract for field %s\n",
                ename);
    return false;
  }
  return true;
}


bool
TMH_taxes_array_valid (const json_t *taxes)
{
  json_t *tax;
  size_t idx;

  if (! json_is_array (taxes))
    return false;
  json_array_foreach (taxes, idx, tax)
  {
    struct TALER_Amount amount;
    const char *name;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_string ("name",
                               &name),
      TALER_JSON_spec_amount_any ("tax",
                                  &amount),
      GNUNET_JSON_spec_end ()
    };
    enum GNUNET_GenericReturnValue res;

    res = TALER_MHD_parse_json_data (NULL,
                                     tax,
                                     spec);
    if (GNUNET_OK != res)
    {
      GNUNET_break_op (0);
      return false;
    }
  }
  return true;
}


struct TMH_WireMethod *
TMH_setup_wire_account (
  struct TALER_FullPayto payto_uri,
  const char *credit_facade_url,
  const json_t *credit_facade_credentials)
{
  struct TMH_WireMethod *wm;
  char *emsg;

  if (NULL != (emsg = TALER_payto_validate (payto_uri)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Invalid URI `%s': %s\n",
                payto_uri.full_payto,
                emsg);
    GNUNET_free (emsg);
    return NULL;
  }

  wm = GNUNET_new (struct TMH_WireMethod);
  if (NULL != credit_facade_url)
    wm->credit_facade_url
      = GNUNET_strdup (credit_facade_url);
  if (NULL != credit_facade_credentials)
    wm->credit_facade_credentials
      = json_incref ((json_t*) credit_facade_credentials);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                              &wm->wire_salt,
                              sizeof (wm->wire_salt));
  wm->payto_uri.full_payto
    = GNUNET_strdup (payto_uri.full_payto);
  TALER_merchant_wire_signature_hash (payto_uri,
                                      &wm->wire_salt,
                                      &wm->h_wire);
  wm->wire_method
    = TALER_payto_get_method (payto_uri.full_payto);
  wm->active = true;
  return wm;
}


enum TALER_ErrorCode
TMH_check_token (const char *token,
                 const char *instance_id,
                 enum TMH_AuthScope *as)
{
  enum TMH_AuthScope scope;
  struct GNUNET_TIME_Timestamp expiration;
  enum GNUNET_DB_QueryStatus qs;
  struct TALER_MERCHANTDB_LoginTokenP btoken;

  if (NULL == token)
  {
    *as = TMH_AS_NONE;
    return TALER_EC_NONE;
  }
  if (0 != strncasecmp (token,
                        RFC_8959_PREFIX,
                        strlen (RFC_8959_PREFIX)))
  {
    *as = TMH_AS_NONE;
    return TALER_EC_NONE;
  }
  token += strlen (RFC_8959_PREFIX);
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (token,
                                     strlen (token),
                                     &btoken,
                                     sizeof (btoken)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Given authorization token `%s' is malformed\n",
                token);
    GNUNET_break_op (0);
    return TALER_EC_GENERIC_TOKEN_MALFORMED;
  }
  qs = TMH_db->select_login_token (TMH_db->cls,
                                   instance_id,
                                   &btoken,
                                   &expiration,
                                   (uint32_t*) &scope);
  if (qs < 0)
  {
    GNUNET_break (0);
    return TALER_EC_GENERIC_DB_FETCH_FAILED;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Authorization token `%s' unknown\n",
                token);
    return TALER_EC_GENERIC_TOKEN_UNKNOWN;
  }
  if (GNUNET_TIME_absolute_is_past (expiration.abs_time))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Authorization token `%s' expired\n",
                token);
    return TALER_EC_GENERIC_TOKEN_EXPIRED;
  }
  *as = scope;
  return TALER_EC_NONE;
}


enum GNUNET_GenericReturnValue
TMH_check_auth_config (struct MHD_Connection *connection,
                       const json_t *jauth,
                       const char **auth_password)
{
  bool auth_wellformed = false;
  const char *auth_method = json_string_value (json_object_get (jauth,
                                                                "method"));

  *auth_password = NULL;
  if (NULL == auth_method)
  {
    GNUNET_break_op (0);
  }
  else if ((GNUNET_YES != TMH_strict_v19) &&
           (0 == strcmp (auth_method,
                         "external")))
  {
    auth_wellformed = true;
  }
  else if (GNUNET_YES == TMH_auth_disabled)
  {
    auth_wellformed = true;
  }
  else if (0 == strcmp (auth_method,
                        "token"))
  {
    json_t *pw_value;

    pw_value = json_object_get (jauth,
                                "password");
    if (NULL == pw_value)
    {
      pw_value = json_object_get (jauth,
                                  "token");
    }
    if (NULL == pw_value)
    {
      auth_wellformed = false;
      GNUNET_break_op (0);
    }
    else
    {
      *auth_password = json_string_value (pw_value);
      if (NULL != *auth_password)
      {
        if (0 == strncasecmp (RFC_8959_PREFIX,
                              *auth_password,
                              strlen (RFC_8959_PREFIX)))
        {
          *auth_password = *auth_password + strlen (RFC_8959_PREFIX);
        }
        auth_wellformed = true;
      }
    }
  }

  if (! auth_wellformed)
  {
    GNUNET_break_op (0);
    return (MHD_YES ==
            TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_BAD_REQUEST,
                                        TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH,
                                        "bad authentication config"))
      ? GNUNET_NO
      : GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


void
TMH_uuid_from_string (const char *uuids,
                      struct GNUNET_Uuid *uuid)
{
  struct GNUNET_HashCode hc;

  GNUNET_CRYPTO_hash (uuids,
                      strlen (uuids),
                      &hc);
  GNUNET_static_assert (sizeof (hc) > sizeof (*uuid));
  GNUNET_memcpy (uuid,
                 &hc,
                 sizeof (*uuid));
}


/**
 * Closure for #trigger_webhook_cb.
 *
 * @param instance which is the instance we work with
 * @param root JSON data to fill into the template
 * @param rv, query of the TALER_TEMPLATEING_fill
 */
struct Trigger
{
  const char *instance;

  const json_t *root;

  enum GNUNET_DB_QueryStatus rv;

};

/**
 * Typically called by `TMH_trigger_webhook`.
 *
 * @param[in,out] cls a `struct Trigger` with information about the webhook
 * @param webhook_serial reference to the configured webhook template.
 * @param event_type is the event/action of the webhook
 * @param url to make request to
 * @param http_method use for the webhook
 * @param header_template of the webhook
 * @param body_template of the webhook
 */
static void
trigger_webhook_cb (void *cls,
                    uint64_t webhook_serial,
                    const char *event_type,
                    const char *url,
                    const char *http_method,
                    const char *header_template,
                    const char *body_template)
{
  struct Trigger *t = cls;
  void *header = NULL;
  void *body = NULL;
  size_t header_size;
  size_t body_size;

  if (NULL != header_template)
  {
    int ret;

    ret = TALER_TEMPLATING_fill (header_template,
                                 t->root,
                                 &header,
                                 &header_size);
    if (0 != ret)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Failed to expand webhook header template for webhook %llu (%d)\n",
                  (unsigned long long) webhook_serial,
                  ret);
      t->rv = GNUNET_DB_STATUS_HARD_ERROR;
      return;
    }
    /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */
    GNUNET_assert ('\0' == ((const char *) header)[header_size]);
  }
  if (NULL != body_template)
  {
    int ret;
    ret = TALER_TEMPLATING_fill (body_template,
                                 t->root,
                                 &body,
                                 &body_size);
    if (0 != ret)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Failed to expand webhook body template for webhook %llu (%d)\n",
                  (unsigned long long) webhook_serial,
                  ret);
      t->rv = GNUNET_DB_STATUS_HARD_ERROR;
      return;
    }
    /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */
    GNUNET_assert ('\0' == ((const char *) body)[body_size]);
  }
  t->rv = TMH_db->insert_pending_webhook (TMH_db->cls,
                                          t->instance,
                                          webhook_serial,
                                          url,
                                          http_method,
                                          header,
                                          body);
  if (t->rv > 0)
  {
    struct GNUNET_DB_EventHeaderP es = {
      .size = htons (sizeof(es)),
      .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING)
    };
    const void *extra = NULL;
    size_t extra_size = 0;
    TMH_db->event_notify (TMH_db->cls,
                          &es,
                          &extra,
                          extra_size);
  }
  free (header);
  free (body);
}


/**
 * TMH_trigger_webhook is a function that need to be use when someone
 * pay. Merchant need to have a notification.
 *
 * @param instance that we need to send the webhook as a notification
 * @param event of the webhook
 * @param args argument of the function
 */
enum GNUNET_DB_QueryStatus
TMH_trigger_webhook (const char *instance,
                     const char *event,
                     const json_t *args)
{
  struct Trigger t = {
    .instance = instance,
    .root = args
  };
  enum GNUNET_DB_QueryStatus qs;

  qs = TMH_db->lookup_webhook_by_event (TMH_db->cls,
                                        instance,
                                        event,
                                        &trigger_webhook_cb,
                                        &t);
  if (qs < 0)
    return qs;
  return t.rv;
}


enum GNUNET_GenericReturnValue
TMH_base_url_by_connection (struct MHD_Connection *connection,
                            const char *instance,
                            struct GNUNET_Buffer *buf)
{
  const char *host;
  const char *forwarded_host;
  const char *forwarded_port;
  const char *uri_path;

  memset (buf,
          0,
          sizeof (*buf));
  if (NULL != TMH_base_url)
  {
    GNUNET_buffer_write_str (buf,
                             TMH_base_url);
  }
  else
  {
    if (GNUNET_YES ==
        TALER_mhd_is_https (connection))
      GNUNET_buffer_write_str (buf,
                               "https://");
    else
      GNUNET_buffer_write_str (buf,
                               "http://");
    host = MHD_lookup_connection_value (connection,
                                        MHD_HEADER_KIND,
                                        MHD_HTTP_HEADER_HOST);
    forwarded_host = MHD_lookup_connection_value (connection,
                                                  MHD_HEADER_KIND,
                                                  "X-Forwarded-Host");
    if (NULL != forwarded_host)
    {
      GNUNET_buffer_write_str (buf,
                               forwarded_host);
    }
    else
    {
      if (NULL == host)
      {
        GNUNET_buffer_clear (buf);
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
      GNUNET_buffer_write_str (buf,
                               host);
    }
    forwarded_port = MHD_lookup_connection_value (connection,
                                                  MHD_HEADER_KIND,
                                                  "X-Forwarded-Port");
    if (NULL != forwarded_port)
    {
      GNUNET_buffer_write_str (buf,
                               ":");
      GNUNET_buffer_write_str (buf,
                               forwarded_port);
    }
    uri_path = MHD_lookup_connection_value (connection,
                                            MHD_HEADER_KIND,
                                            "X-Forwarded-Prefix");
    if (NULL != uri_path)
      GNUNET_buffer_write_path (buf,
                                uri_path);
  }
  if (0 != strcmp (instance,
                   "admin"))
  {
    GNUNET_buffer_write_path (buf,
                              "/instances/");
    GNUNET_buffer_write_str (buf,
                             instance);
  }
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
TMH_taler_uri_by_connection (struct MHD_Connection *connection,
                             const char *method,
                             const char *instance,
                             struct GNUNET_Buffer *buf)
{
  const char *host;
  const char *forwarded_host;
  const char *forwarded_port;
  const char *uri_path;

  memset (buf,
          0,
          sizeof (*buf));
  host = MHD_lookup_connection_value (connection,
                                      MHD_HEADER_KIND,
                                      "Host");
  forwarded_host = MHD_lookup_connection_value (connection,
                                                MHD_HEADER_KIND,
                                                "X-Forwarded-Host");
  forwarded_port = MHD_lookup_connection_value (connection,
                                                MHD_HEADER_KIND,
                                                "X-Forwarded-Port");
  uri_path = MHD_lookup_connection_value (connection,
                                          MHD_HEADER_KIND,
                                          "X-Forwarded-Prefix");
  if (NULL != forwarded_host)
    host = forwarded_host;
  if (NULL == host)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  GNUNET_buffer_write_str (buf,
                           "taler");
  if (GNUNET_NO == TALER_mhd_is_https (connection))
    GNUNET_buffer_write_str (buf,
                             "+http");
  GNUNET_buffer_write_str (buf,
                           "://");
  GNUNET_buffer_write_str (buf,
                           method);
  GNUNET_buffer_write_str (buf,
                           "/");
  GNUNET_buffer_write_str (buf,
                           host);
  if (NULL != forwarded_port)
  {
    GNUNET_buffer_write_str (buf,
                             ":");
    GNUNET_buffer_write_str (buf,
                             forwarded_port);
  }
  if (NULL != uri_path)
    GNUNET_buffer_write_path (buf,
                              uri_path);
  if (0 != strcmp ("admin",
                   instance))
  {
    GNUNET_buffer_write_path (buf,
                              "instances");
    GNUNET_buffer_write_path (buf,
                              instance);
  }
  return GNUNET_OK;
}


/**
 * Closure for #add_matching_account().
 */
struct ExchangeMatchContext
{
  /**
   * Wire method to match, NULL for all.
   */
  const char *wire_method;

  /**
   * Array of accounts to return.
   */
  json_t *accounts;
};


/**
 * If the given account is feasible, add it to the array
 * of accounts we return.
 *
 * @param cls a `struct PostReserveContext`
 * @param payto_uri URI of the account
 * @param conversion_url URL of a conversion service
 * @param debit_restrictions restrictions for debits from account
 * @param credit_restrictions restrictions for credits to account
 * @param master_sig signature affirming the account
 */
static void
add_matching_account (
  void *cls,
  struct TALER_FullPayto payto_uri,
  const char *conversion_url,
  const json_t *debit_restrictions,
  const json_t *credit_restrictions,
  const struct TALER_MasterSignatureP *master_sig)
{
  struct ExchangeMatchContext *rc = cls;
  char *method;

  method = TALER_payto_get_method (payto_uri.full_payto);
  if ( (NULL == rc->wire_method) ||
       (0 == strcmp (method,
                     rc->wire_method)) )
  {
    json_t *acc;

    acc = GNUNET_JSON_PACK (
      TALER_JSON_pack_full_payto ("payto_uri",
                                  payto_uri),
      GNUNET_JSON_pack_data_auto ("master_sig",
                                  master_sig),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("conversion_url",
                                 conversion_url)),
      GNUNET_JSON_pack_array_incref ("credit_restrictions",
                                     (json_t *) credit_restrictions),
      GNUNET_JSON_pack_array_incref ("debit_restrictions",
                                     (json_t *) debit_restrictions)
      );
    GNUNET_assert (0 ==
                   json_array_append_new (rc->accounts,
                                          acc));
  }
  GNUNET_free (method);
}


/**
 * Return JSON array with all of the exchange accounts
 * that support the given @a wire_method.
 *
 * @param master_pub master public key to match exchange by
 * @param wire_method NULL for any
 * @return JSON array with information about all matching accounts
 */
json_t *
TMH_exchange_accounts_by_method (
  const struct TALER_MasterPublicKeyP *master_pub,
  const char *wire_method)
{
  struct ExchangeMatchContext emc = {
    .wire_method = wire_method,
    .accounts = json_array ()
  };
  enum GNUNET_DB_QueryStatus qs;

  GNUNET_assert (NULL != emc.accounts);
  qs = TMH_db->select_accounts_by_exchange (TMH_db->cls,
                                            master_pub,
                                            &add_matching_account,
                                            &emc);
  if (qs < 0)
  {
    json_decref (emc.accounts);
    return NULL;
  }
  return emc.accounts;
}


char *
TMH_make_order_status_url (struct MHD_Connection *con,
                           const char *order_id,
                           const char *session_id,
                           const char *instance_id,
                           struct TALER_ClaimTokenP *claim_token,
                           struct TALER_PrivateContractHashP *h_contract)
{
  struct GNUNET_Buffer buf;
  /* Number of query parameters written so far */
  unsigned int num_qp = 0;

  GNUNET_assert (NULL != instance_id);
  GNUNET_assert (NULL != order_id);
  if (GNUNET_OK !=
      TMH_base_url_by_connection (con,
                                  instance_id,
                                  &buf))
  {
    GNUNET_break (0);
    return NULL;
  }
  GNUNET_buffer_write_path (&buf,
                            "/orders");
  GNUNET_buffer_write_path (&buf,
                            order_id);
  if ( (NULL != claim_token) &&
       (! GNUNET_is_zero (claim_token)) )
  {
    /* 'token=' for human readability */
    GNUNET_buffer_write_str (&buf,
                             "?token=");
    GNUNET_buffer_write_data_encoded (&buf,
                                      (char *) claim_token,
                                      sizeof (*claim_token));
    num_qp++;
  }

  if (NULL != session_id)
  {
    if (num_qp > 0)
      GNUNET_buffer_write_str (&buf,
                               "&session_id=");
    else
      GNUNET_buffer_write_str (&buf,
                               "?session_id=");
    GNUNET_buffer_write_str (&buf,
                             session_id);
    num_qp++;
  }

  if (NULL != h_contract)
  {
    if (num_qp > 0)
      GNUNET_buffer_write_str (&buf,
                               "&h_contract=");
    else
      GNUNET_buffer_write_str (&buf,
                               "?h_contract=");
    GNUNET_buffer_write_data_encoded (&buf,
                                      (char *) h_contract,
                                      sizeof (*h_contract));
  }

  return GNUNET_buffer_reap_str (&buf);
}


char *
TMH_make_taler_pay_uri (struct MHD_Connection *con,
                        const char *order_id,
                        const char *session_id,
                        const char *instance_id,
                        struct TALER_ClaimTokenP *claim_token)
{
  struct GNUNET_Buffer buf;

  GNUNET_assert (NULL != instance_id);
  GNUNET_assert (NULL != order_id);
  if (GNUNET_OK !=
      TMH_taler_uri_by_connection (con,
                                   "pay",
                                   instance_id,
                                   &buf))
  {
    GNUNET_break (0);
    return NULL;
  }
  GNUNET_buffer_write_path (&buf,
                            order_id);
  GNUNET_buffer_write_path (&buf,
                            (NULL == session_id)
                            ? ""
                            : session_id);
  if ( (NULL != claim_token) &&
       (! GNUNET_is_zero (claim_token)))
  {
    /* Just 'c=' because this goes into QR
       codes, so this is more compact. */
    GNUNET_buffer_write_str (&buf,
                             "?c=");
    GNUNET_buffer_write_data_encoded (&buf,
                                      (char *) claim_token,
                                      sizeof (struct TALER_ClaimTokenP));
  }

  return GNUNET_buffer_reap_str (&buf);
}
