/*
 This file is part of GNU Taler
 (C) 2024 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU Affero General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU 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 Affero General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>

 SPDX-License-Identifier: AGPL-3.0-or-later
 */

import { codecForAmountString } from "./amounts.js";
import {
  Codec,
  buildCodecForObject,
  codecForAny,
  codecForBoolean,
  codecForList,
  codecForNumber,
  codecForString,
  codecOptional,
} from "./codec.js";
import {
  AccessToken,
  AccountLimit,
  DenomKeyType,
  ExchangeWireAccount,
  ObjectCodec,
  PaytoString,
  TalerPreciseTimestamp,
  UnblindedDenominationSignature,
  assertUnreachable,
  buildCodecForUnion,
  codecForAccountLimit,
  codecForConstNumber,
  codecForConstString,
  codecForEither,
  codecForExchangeWireAccount,
  codecForMap,
  codecForPaytoString,
  codecForPreciseTimestamp,
  codecForStringURL,
  codecForTalerUriString,
  codecOptionalDefault,
} from "./index.js";
import {
  AbsoluteTime,
  TalerProtocolDuration,
  TalerProtocolTimestamp,
  codecForDuration,
  codecForTimestamp,
} from "./time.js";
import {
  AmountString,
  Base32String,
  ClaimToken,
  CoinPublicKey,
  Cs25519Point,
  Cs25519Scalar,
  CurrencySpecification,
  EddsaPublicKey,
  EddsaPublicKeyString,
  EddsaSignatureString,
  HashCode,
  HashCodeString,
  ImageDataUrl,
  Integer,
  InternationalizedString,
  RelativeTime,
  RsaPublicKey,
  RsaSignature,
  Timestamp,
  WireTransferIdentifierRawP,
  codecForAccessToken,
  codecForCurrencySpecificiation,
  codecForEddsaPublicKey,
  codecForEddsaSignature,
  codecForInternationalizedString,
  codecForURLString,
} from "./types-taler-common.js";

/**
 * Proposal returned from the contract URL.
 *
 * Doc name: api-merchant/ClaimResponse.
 */
export interface MerchantClaimResponse {
  /**
   * Contract terms for the propoal.
   * Raw, un-decoded JSON object.
   */
  contract_terms: any;

  /**
   * Signature over contract, made by the merchant.  The public key used for signing
   * must be contract_terms.merchant_pub.
   */
  sig: string;
}

export type TokenEnvelope = TokenEnvelopeRsa | TokenEnvelopeCs;

export interface TokenEnvelopeRsa {
  cipher: DenomKeyType.Rsa;
  rsa_blinded_planchet: string;
}

export interface TokenEnvelopeCs {
  cipher: DenomKeyType.ClauseSchnorr;
  cs_nonce: string;
  cs_blinded_c0: string; // Crockford base32 encoded
  cs_blinded_c1: string; // Crockford base32 encoded
}

export interface SignedTokenEnvelope {
  blind_sig: TokenIssueBlindSig;
}

export type TokenIssueBlindSig = RSATokenIssueBlindSig | CSTokenIssueBlindSig;

export interface RSATokenIssueBlindSig {
  cipher: DenomKeyType.Rsa;
  blinded_rsa_signature: string;
}

export interface CSTokenIssueBlindSig {
  cipher: DenomKeyType.ClauseSchnorr;
  b: Integer;
  s: Cs25519Scalar;
}

export interface TokenUseSig {
  token_sig: EddsaSignatureString;
  token_pub: EddsaPublicKeyString;
  ub_sig: UnblindedDenominationSignature;
  h_issue: string;
}

export interface MerchantPayResponse {
  sig: string;
  pos_confirmation?: string;
  token_sigs?: SignedTokenEnvelope[];
}

interface MerchantOrderStatusPaid {
  // Was the payment refunded (even partially, via refund or abort)?
  refunded: boolean;

  // Is any amount of the refund still waiting to be picked up (even partially)?
  refund_pending: boolean;

  // Amount that was refunded in total.
  refund_amount: AmountString;

  // Amount that already taken by the wallet.
  refund_taken: AmountString;
}

interface MerchantOrderRefundResponse {
  /**
   * Amount that was refunded in total.
   */
  refund_amount: AmountString;

  /**
   * Successful refunds for this payment, empty array for none.
   */
  refunds: MerchantCoinRefundStatus[];

  /**
   * Public key of the merchant.
   */
  merchant_pub: EddsaPublicKeyString;
}

/**
 * Response from the internal merchant API.
 */
export class CheckPaymentResponse {
  order_status: string;
  refunded: boolean | undefined;
  refunded_amount: string | undefined;
  contract_terms: any | undefined;
  taler_pay_uri: string | undefined;
  contract_url: string | undefined;
}

export const codecForMerchantRefundPermission =
  (): Codec<MerchantAbortPayRefundDetails> =>
    buildCodecForObject<MerchantAbortPayRefundDetails>()
      .property("refund_amount", codecForAmountString())
      .property("refund_fee", codecForAmountString())
      .property("coin_pub", codecForString())
      .property("rtransaction_id", codecForNumber())
      .property("exchange_http_status", codecForNumber())
      .property("exchange_code", codecOptional(codecForNumber()))
      .property("exchange_reply", codecOptional(codecForAny()))
      .property("exchange_sig", codecOptional(codecForString()))
      .property("exchange_pub", codecOptional(codecForString()))
      .build("MerchantRefundPermission");

export const codecForMerchantClaimResponse = (): Codec<MerchantClaimResponse> =>
  buildCodecForObject<MerchantClaimResponse>()
    .property("contract_terms", codecForMerchantContractTerms())
    .property("sig", codecForString())
    .build("MerchantClaimResponse");

export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> =>
  buildCodecForObject<CheckPaymentResponse>()
    .property("order_status", codecForString())
    .property("refunded", codecOptional(codecForBoolean()))
    .property("refunded_amount", codecOptional(codecForString()))
    .property("contract_terms", codecOptional(codecForAny()))
    .property("taler_pay_uri", codecOptional(codecForString()))
    .property("contract_url", codecOptional(codecForString()))
    .build("CheckPaymentResponse");

export type MerchantCoinRefundStatus =
  | MerchantCoinRefundSuccessStatus
  | MerchantCoinRefundFailureStatus;

export interface MerchantCoinRefundSuccessStatus {
  type: "success";

  // HTTP status of the exchange request, 200 (integer) required for refund confirmations.
  exchange_status: 200;

  // the EdDSA :ref:signature (binary-only) with purpose
  // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the
  // exchange affirming the successful refund
  exchange_sig: EddsaSignatureString;

  // public EdDSA key of the exchange that was used to generate the signature.
  // Should match one of the exchange's signing keys from /keys.  It is given
  // explicitly as the client might otherwise be confused by clock skew as to
  // which signing key was used.
  exchange_pub: EddsaPublicKeyString;

  // Refund transaction ID.
  rtransaction_id: number;

  // public key of a coin that was refunded
  coin_pub: EddsaPublicKeyString;

  // Amount that was refunded, including refund fee charged by the exchange
  // to the customer.
  refund_amount: AmountString;

  execution_time: TalerProtocolTimestamp;
}

export interface MerchantCoinRefundFailureStatus {
  type: "failure";

  // HTTP status of the exchange request, must NOT be 200.
  exchange_status: number;

  // Taler error code from the exchange reply, if available.
  exchange_code?: number;

  // If available, HTTP reply from the exchange.
  exchange_reply?: any;

  // Refund transaction ID.
  rtransaction_id: number;

  // public key of a coin that was refunded
  coin_pub: EddsaPublicKeyString;

  // Amount that was refunded, including refund fee charged by the exchange
  // to the customer.
  refund_amount: AmountString;

  execution_time: TalerProtocolTimestamp;
}

export interface MerchantOrderStatusUnpaid {
  /**
   * URI that the wallet must process to complete the payment.
   */
  taler_pay_uri: string;

  /**
   * Alternative order ID which was paid for already in the same session.
   *
   * Only given if the same product was purchased before in the same session.
   */
  already_paid_order_id?: string;
}

export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> =>
  buildCodecForObject<MerchantPayResponse>()
    .property("sig", codecForString())
    .property("pos_confirmation", codecOptional(codecForString()))
    .property(
      "token_sigs",
      codecOptional(codecForList(codecForSignedTokenEnvelope())),
    )
    .build("MerchantPayResponse");

export const codecForSignedTokenEnvelope = (): Codec<SignedTokenEnvelope> =>
  buildCodecForObject<SignedTokenEnvelope>()
    .property("blind_sig", codecForTokenIssueBlindSig())
    .build("SignedTokenEnvelope");

export const codecForTokenIssueBlindSig = (): Codec<TokenIssueBlindSig> =>
  buildCodecForUnion<TokenIssueBlindSig>()
    .discriminateOn("cipher")
    .alternative(DenomKeyType.Rsa, codecForRSATokenIssueBlindSig())
    .alternative(DenomKeyType.ClauseSchnorr, codecForCsTokenIssueBlindSig())
    .build("TokenIssueBlindSig");

export const codecForRSATokenIssueBlindSig = (): Codec<RSATokenIssueBlindSig> =>
  buildCodecForObject<RSATokenIssueBlindSig>()
    .property("cipher", codecForConstString(DenomKeyType.Rsa))
    .property("blinded_rsa_signature", codecForString())
    .build("RSATokenIssueBlindSig");

export const codecForCsTokenIssueBlindSig = (): Codec<CSTokenIssueBlindSig> =>
  buildCodecForObject<CSTokenIssueBlindSig>()
    .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr))
    .property("b", codecForNumber())
    .property("s", codecForString())
    .build("CSTokenIssueBlindSig");

export const codecForMerchantOrderStatusPaid =
  (): Codec<MerchantOrderStatusPaid> =>
    buildCodecForObject<MerchantOrderStatusPaid>()
      .property("refund_amount", codecForAmountString())
      .property("refund_taken", codecForAmountString())
      .property("refund_pending", codecForBoolean())
      .property("refunded", codecForBoolean())
      .build("MerchantOrderStatusPaid");

export const codecForMerchantOrderStatusUnpaid =
  (): Codec<MerchantOrderStatusUnpaid> =>
    buildCodecForObject<MerchantOrderStatusUnpaid>()
      .property("taler_pay_uri", codecForString())
      .property("already_paid_order_id", codecOptional(codecForString()))
      .build("MerchantOrderStatusUnpaid");

export interface AbortRequest {
  // hash of the order's contract terms (this is used to authenticate the
  // wallet/customer in case $ORDER_ID is guessable).
  h_contract: string;

  // List of coins the wallet would like to see refunds for.
  // (Should be limited to the coins for which the original
  // payment succeeded, as far as the wallet knows.)
  coins: AbortingCoin[];
}

export interface AbortingCoin {
  // Public key of a coin for which the wallet is requesting an abort-related refund.
  coin_pub: EddsaPublicKeyString;

  // The amount to be refunded (matches the original contribution)
  // @deprecated since **v18**.
  /**
   * @deprecated
   */
  contribution?: AmountString;

  // URL of the exchange this coin was withdrawn from.
  exchange_url: string;
}

export interface AbortResponse {
  // List of refund responses about the coins that the wallet
  // requested an abort for.  In the same order as the 'coins'
  // from the original request.
  // The rtransaction_id is implied to be 0.
  refunds: MerchantAbortPayRefundStatus[];
}

export type MerchantAbortPayRefundStatus =
  | MerchantAbortPayRefundSuccessStatus
  | MerchantAbortPayRefundFailureStatus
  | MerchantAbortPayRefundUndepositedStatus;

export interface MerchantAbortPayRefundUndepositedStatus {
  // Used as tag for the sum type RefundStatus sum type.
  type: "undeposited";
}

// Details about why a refund failed.
export interface MerchantAbortPayRefundFailureStatus {
  // Used as tag for the sum type RefundStatus sum type.
  type: "failure";

  // HTTP status of the exchange request, must NOT be 200.
  exchange_status: number;

  // Taler error code from the exchange reply, if available.
  exchange_code?: number;

  // If available, HTTP reply from the exchange.
  exchange_reply?: unknown;
}

// Additional details needed to verify the refund confirmation signature
// (h_contract_terms and merchant_pub) are already known
// to the wallet and thus not included.
export interface MerchantAbortPayRefundSuccessStatus {
  // Used as tag for the sum type MerchantCoinRefundStatus sum type.
  type: "success";

  // HTTP status of the exchange request, 200 (integer) required for refund confirmations.
  exchange_status: 200;

  // the EdDSA :ref:signature (binary-only) with purpose
  // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the
  // exchange affirming the successful refund
  exchange_sig: string;

  // public EdDSA key of the exchange that was used to generate the signature.
  // Should match one of the exchange's signing keys from /keys.  It is given
  // explicitly as the client might otherwise be confused by clock skew as to
  // which signing key was used.
  exchange_pub: string;
}

export interface AuditorHandle {
  /**
   * Official name of the auditor.
   */
  name: string;

  /**
   * Master public signing key of the auditor.
   */
  auditor_pub: EddsaPublicKeyString;

  /**
   * Base URL of the auditor.
   */
  url: string;
}

// Delivery location, loosely modeled as a subset of
// ISO20022's PostalAddress25.
export interface Location {
  // Nation with its own government.
  country?: string;

  // Identifies a subdivision of a country such as state, region, county.
  country_subdivision?: string;

  // Identifies a subdivision within a country sub-division.
  district?: string;

  // Name of a built-up area, with defined boundaries, and a local government.
  town?: string;

  // Specific location name within the town.
  town_location?: string;

  // Identifier consisting of a group of letters and/or numbers that
  // is added to a postal address to assist the sorting of mail.
  post_code?: string;

  // Name of a street or thoroughfare.
  street?: string;

  // Name of the building or house.
  building_name?: string;

  // Number that identifies the position of a building on a street.
  building_number?: string;

  // Free-form address lines, should not exceed 7 elements.
  address_lines?: string[];
}

export interface MerchantInfo {
  // The merchant's legal name of business.
  name: string;

  // Label for a location with the business address of the merchant.
  email?: string;

  // Label for a location with the business address of the merchant.
  website?: string;

  // An optional base64-encoded product image.
  logo?: ImageDataUrl;

  // Label for a location with the business address of the merchant.
  address?: Location;

  // Label for a location that denotes the jurisdiction for disputes.
  // Some of the typical fields for a location (such as a street address) may be absent.
  jurisdiction?: Location;
}

export interface Tax {
  // the name of the tax
  name: string;

  // amount paid in tax
  tax: AmountString;
}

export interface Product {
  // merchant-internal identifier for the product.
  product_id?: string;

  // Human-readable product description.
  description: string;

  // Map from IETF BCP 47 language tags to localized descriptions
  description_i18n?: InternationalizedString;

  // The number of units of the product to deliver to the customer.
  quantity?: Integer;

  // The unit in which the product is measured (liters, kilograms, packages, etc.)
  unit?: string;

  // The price of the product; this is the total price for quantity times unit of this product.
  price?: AmountString;

  // An optional base64-encoded product image
  image?: ImageDataUrl;

  // a list of taxes paid by the merchant for this product. Can be empty.
  taxes?: Tax[];

  // time indicating when this product should be delivered
  delivery_date?: TalerProtocolTimestamp;
}

/**
 * Contract terms from a merchant.
 */
interface MerchantContractTermsCommon {
  // The hash of the merchant instance's wire details.
  h_wire: string;

  // Specifies for how long the wallet should try to get an
  // automatic refund for the purchase. If this field is
  // present, the wallet should wait for a few seconds after
  // the purchase and then automatically attempt to obtain
  // a refund.  The wallet should probe until "delay"
  // after the payment was successful (i.e. via long polling
  // or via explicit requests with exponential back-off).
  //
  // In particular, if the wallet is offline
  // at that time, it MUST repeat the request until it gets
  // one response from the merchant after the delay has expired.
  // If the refund is granted, the wallet MUST automatically
  // recover the payment.  This is used in case a merchant
  // knows that it might be unable to satisfy the contract and
  // desires for the wallet to attempt to get the refund without any
  // customer interaction.  Note that it is NOT an error if the
  // merchant does not grant a refund.
  auto_refund?: TalerProtocolDuration;

  // Wire transfer method identifier for the wire method associated with h_wire.
  // The wallet may only select exchanges via a matching auditor if the
  // exchange also supports this wire method.
  // The wire transfer fees must be added based on this wire transfer method.
  wire_method: string;

  // Human-readable description of the whole purchase.
  summary: string;

  // Map from IETF BCP 47 language tags to localized summaries.
  summary_i18n?: InternationalizedString;

  // Unique, free-form identifier for the proposal.
  // Must be unique within a merchant instance.
  // For merchants that do not store proposals in their DB
  // before the customer paid for them, the order_id can be used
  // by the frontend to restore a proposal from the information
  // encoded in it (such as a short product identifier and timestamp).
  order_id: string;

  // Nonce generated by the wallet and echoed by the merchant
  // in this field when the proposal is generated.
  nonce: string;

  // After this deadline, the merchant won't accept payments for the contract.
  pay_deadline: TalerProtocolTimestamp;

  // More info about the merchant, see below.
  merchant: MerchantInfo;

  // Merchant's public key used to sign this proposal; this information
  // is typically added by the backend. Note that this can be an ephemeral key.
  merchant_pub: string;

  // Time indicating when the order should be delivered.
  // May be overwritten by individual products.
  delivery_date?: TalerProtocolTimestamp;

  // Delivery location for (all!) products.
  delivery_location?: Location;

  // Exchanges that the merchant accepts even if it does not accept any auditors that audit them.
  exchanges: Exchange[];

  // List of products that are part of the purchase (see Product).
  products?: Product[];

  // After this deadline has passed, no refunds will be accepted.
  refund_deadline: TalerProtocolTimestamp;

  // Transfer deadline for the exchange.  Must be in the
  // deposit permissions of coins used to pay for this order.
  wire_transfer_deadline: TalerProtocolTimestamp;

  // Time when this contract was generated.
  timestamp: TalerProtocolTimestamp;

  // Base URL of the (public!) merchant backend API.
  // Must be an absolute URL that ends with a slash.
  merchant_base_url: string;

  // URL that will show that the order was successful after
  // it has been paid for.  Optional, but either fulfillment_url
  // or fulfillment_message must be specified in every
  // contract terms.
  //
  // If a non-unique fulfillment URL is used, a customer can only
  // buy the order once and will be redirected to a previous purchase
  // when trying to buy an order with the same fulfillment URL a second
  // time. This is useful for digital goods that a customer only needs
  // to buy once but should be able to repeatedly download.
  //
  // For orders where the customer is expected to be able to make
  // repeated purchases (for equivalent goods), the fulfillment URL
  // should be made unique for every order. The easiest way to do
  // this is to include a unique order ID in the fulfillment URL.
  //
  // When POSTing to the merchant, the placeholder text "${ORDER_ID}"
  // is be replaced with the actual order ID (useful if the
  // order ID is generated server-side and needs to be
  // in the URL). Note that this placeholder can only be used once.
  // Front-ends may use other means to generate a unique fulfillment URL.
  fulfillment_url?: string;

  // URL where the same contract could be ordered again (if
  // available). Returned also at the public order endpoint
  // for people other than the actual buyer (hence public,
  // in case order IDs are guessable).
  public_reorder_url?: string;

  // Message shown to the customer after paying for the order.
  // Either fulfillment_url or fulfillment_message must be specified.
  fulfillment_message?: string;

  // Map from IETF BCP 47 language tags to localized fulfillment
  // messages.
  fulfillment_message_i18n?: InternationalizedString;

  // Extra data that is only interpreted by the merchant frontend.
  // Useful when the merchant needs to store extra information on a
  // contract without storing it separately in their database.
  // Must really be an Object (not a string, integer, float or array).
  extra?: any;

  // Minimum age the buyer must have (in years). Default is 0.
  // This value is at least as large as the maximum over all
  // minimum age requirements of the products in this contract.
  // It might also be set independent of any product, due to
  // legal requirements.
  minimum_age?: Integer;
}

export enum MerchantContractVersion {
  V0 = 0,
  V1 = 1,
}

export interface MerchantContractTermsV0 extends MerchantContractTermsCommon {
  version?: MerchantContractVersion.V0;

  // Total price for the transaction.
  // The exchange will subtract deposit fees from that amount
  // before transferring it to the merchant.
  amount: AmountString;

  // Maximum total deposit fee accepted by the merchant for this contract.
  // Overrides defaults of the merchant instance.
  max_fee: AmountString;
}

export interface MerchantContractTermsV1 extends MerchantContractTermsCommon {
  version: MerchantContractVersion.V1;

  // List of contract choices that the customer can select from.
  // @since protocol **vSUBSCRIBE**
  choices: MerchantContractChoice[];

  // Map of storing metadata and issue keys of
  // token families referenced in this contract.
  // @since protocol **vSUBSCRIBE**
  token_families: { [token_family_slug: string]: MerchantContractTokenFamily };
}

export type MerchantContractTerms =
  | MerchantContractTermsV0
  | MerchantContractTermsV1;

export interface MerchantContractChoice {
  // Price to be paid for this choice. Could be 0.
  // The price is in addition to other instruments,
  // such as rations and tokens.
  // The exchange will subtract deposit fees from that amount
  // before transferring it to the merchant.
  amount: AmountString;

  // Human readable description of the semantics of the choice
  // within the contract to be shown to the user at payment.
  description?: string;

  // Map from IETF 47 language tags to localized descriptions.
  description_i18n?: InternationalizedString;

  // List of inputs the wallet must provision (all of them) to
  // satisfy the conditions for the contract.
  inputs: MerchantContractInput[];

  // List of outputs the merchant promises to yield (all of them)
  // once the contract is paid.
  outputs: MerchantContractOutput[];

  // Maximum total deposit fee accepted by the merchant for this contract.
  max_fee: AmountString;
}

export enum MerchantContractInputType {
  Token = "token",
}

export type MerchantContractInput = MerchantContractInputToken;

export interface MerchantContractInputToken {
  type: MerchantContractInputType.Token;

  // Slug of the token family in the
  // token_families map on the order.
  token_family_slug: string;

  // Number of tokens of this type required.
  // Defaults to one if the field is not provided.
  count?: Integer;
}

export enum MerchantContractOutputType {
  Token = "token",
  TaxReceipt = "tax-receipt",
}

export type MerchantContractOutput =
  | MerchantContractOutputToken
  | MerchantContractOutputTaxReceipt;

export interface MerchantContractOutputToken {
  type: MerchantContractOutputType.Token;

  // Slug of the token family in the
  // 'token_families' map on the top-level.
  token_family_slug: string;

  // Number of tokens to be issued.
  // Defaults to one if the field is not provided.
  count?: Integer;

  // Index of the public key for this output token
  // in the ContractTokenFamily keys array.
  key_index: Integer;
}

export interface MerchantContractOutputTaxReceipt {
  type: MerchantContractOutputType.TaxReceipt;

  // Array of base URLs of donation authorities that can be
  // used to issue the tax receipts. The client must select one.
  donau_urls: string[];

  // Total amount that will be on the tax receipt.
  // Optional, if missing the full amount will be on the receipt.
  amount?: AmountString;
}

export interface MerchantContractTokenFamily {
  // Human readable name of the token family.
  name: string;

  // Human-readable description of the semantics of
  // this token family (for display).
  description: string;

  // Map from IETF BCP 47 language tags to localized descriptions.
  description_i18n?: { [lang_tag: string]: string };

  // Public keys used to validate tokens issued by this token family.
  keys: TokenIssuePublicKey[];

  // Kind-specific information of the token
  details: MerchantContractTokenDetails;

  // Must a wallet understand this token type to
  // process contracts that use or issue it?
  critical: boolean;
}

export type TokenIssuePublicKey =
  | TokenIssueRsaPublicKey
  | TokenIssueCsPublicKey;

export interface TokenIssueRsaPublicKey {
  cipher: "RSA";

  // RSA public key.
  rsa_pub: RsaPublicKey;

  // Start time of this key's signatures validity period.
  signature_validity_start: Timestamp;

  // End time of this key's signatures validity period.
  signature_validity_end: Timestamp;
}

export interface TokenIssueCsPublicKey {
  cipher: "CS";

  // CS public key.
  cs_pub: Cs25519Point;

  // Start time of this key's signatures validity period.
  signature_validity_start: Timestamp;

  // End time of this key's signatures validity period.
  signature_validity_end: Timestamp;
}

export enum MerchantContractTokenKind {
  Subscription = "subscription",
  Discount = "discount",
}

export type MerchantContractTokenDetails =
  | MerchantContractSubscriptionTokenDetails
  | MerchantContractDiscountTokenDetails;

export interface MerchantContractSubscriptionTokenDetails {
  class: MerchantContractTokenKind.Subscription;

  // Array of domain names where this subscription
  // can be safely used (e.g. the issuer warrants that
  // these sites will re-issue tokens of this type
  // if the respective contract says so).  May contain
  // "*" for any domain or subdomain.
  trusted_domains: string[];
}

export interface MerchantContractDiscountTokenDetails {
  class: MerchantContractTokenKind.Discount;

  // Array of domain names where this discount token
  // is intended to be used.  May contain "*" for any
  // domain or subdomain.  Users should be warned about
  // sites proposing to consume discount tokens of this
  // type that are not in this list that the merchant
  // is accepting a coupon from a competitor and thus
  // may be attaching different semantics (like get 20%
  // discount for my competitors 30% discount token).
  expected_domains: string[];
}

/**
 * Refund permission in the format that the merchant gives it to us.
 */
export interface MerchantAbortPayRefundDetails {
  /**
   * Amount to be refunded.
   */
  refund_amount: string;

  /**
   * Fee for the refund.
   */
  refund_fee: string;

  /**
   * Public key of the coin being refunded.
   */
  coin_pub: string;

  /**
   * Refund transaction ID between merchant and exchange.
   */
  rtransaction_id: number;

  /**
   * Exchange's key used for the signature.
   */
  exchange_pub?: string;

  /**
   * Exchange's signature to confirm the refund.
   */
  exchange_sig?: string;

  /**
   * Error replay from the exchange (if any).
   */
  exchange_reply?: any;

  /**
   * Error code from the exchange (if any).
   */
  exchange_code?: number;

  /**
   * HTTP status code of the exchange's response
   * to the merchant's refund request.
   */
  exchange_http_status: number;
}

/**
 * Reserve signature, defined as separate class to facilitate
 * schema validation.
 */
export interface MerchantBlindSigWrapperV1 {
  /**
   * Reserve signature.
   */
  blind_sig: string;
}

export const codecForAuditorHandle = (): Codec<AuditorHandle> =>
  buildCodecForObject<AuditorHandle>()
    .property("name", codecForString())
    .property("auditor_pub", codecForEddsaPublicKey())
    .property("url", codecForString())
    .build("AuditorHandle");

export const codecForLocation = (): Codec<Location> =>
  buildCodecForObject<Location>()
    .property("country", codecOptional(codecForString()))
    .property("country_subdivision", codecOptional(codecForString()))
    .property("building_name", codecOptional(codecForString()))
    .property("building_number", codecOptional(codecForString()))
    .property("district", codecOptional(codecForString()))
    .property("street", codecOptional(codecForString()))
    .property("post_code", codecOptional(codecForString()))
    .property("town", codecOptional(codecForString()))
    .property("town_location", codecOptional(codecForString()))
    .property("address_lines", codecOptional(codecForList(codecForString())))
    .build("Location");

export const codecForMerchantInfo = (): Codec<MerchantInfo> =>
  buildCodecForObject<MerchantInfo>()
    .property("name", codecForString())
    .property("address", codecOptional(codecForLocation()))
    .property("jurisdiction", codecOptional(codecForLocation()))
    .build("MerchantInfo");

const codecForMerchantContractTermsCommon =
  (): ObjectCodec<MerchantContractTermsCommon> =>
    buildCodecForObject<MerchantContractTermsCommon>()
      .property("order_id", codecForString())
      .property("fulfillment_url", codecOptional(codecForString()))
      .property("fulfillment_message", codecOptional(codecForString()))
      .property(
        "fulfillment_message_i18n",
        codecOptional(codecForInternationalizedString()),
      )
      .property("public_reorder_url", codecOptional(codecForString()))
      .property("merchant_base_url", codecForString())
      .property("h_wire", codecForString())
      .property("auto_refund", codecOptional(codecForDuration))
      .property("wire_method", codecForString())
      .property("summary", codecForString())
      .property(
        "summary_i18n",
        codecOptional(codecForInternationalizedString()),
      )
      .property("nonce", codecForString())
      .property("pay_deadline", codecForTimestamp)
      .property("refund_deadline", codecForTimestamp)
      .property("wire_transfer_deadline", codecForTimestamp)
      .property("timestamp", codecForTimestamp)
      .property("delivery_location", codecOptional(codecForLocation()))
      .property("delivery_date", codecOptional(codecForTimestamp))
      .property("merchant", codecForMerchantInfo())
      .property("merchant_pub", codecForString())
      .property("exchanges", codecForList(codecForExchange()))
      .property("products", codecOptional(codecForList(codecForProduct())))
      .property("extra", codecForAny())
      .property("minimum_age", codecOptional(codecForNumber()))
      .build("TalerMerchantApi.ContractTermsCommon");

export const codecForMerchantContractTermsV0 =
  (): Codec<MerchantContractTermsV0> =>
    buildCodecForObject<MerchantContractTermsV0>()
      .property(
        "version",
        codecOptional(codecForConstNumber(MerchantContractVersion.V0)),
      )
      .property("amount", codecForAmountString())
      .property("max_fee", codecForAmountString())
      .mixin(codecForMerchantContractTermsCommon())
      .build("TalerMerchantApi.ContractTermsV0");

export const codecForMerchantContractTermsV1 =
  (): Codec<MerchantContractTermsV1> =>
    buildCodecForObject<MerchantContractTermsV1>()
      .property("version", codecForConstNumber(MerchantContractVersion.V1))
      .property("choices", codecForList(codecForMerchantContractChoice()))
      .property(
        "token_families",
        codecForMap(codecForMerchantContractTokenFamily()),
      )
      .mixin(codecForMerchantContractTermsCommon())
      .build("TalerMerchantApi.ContractTermsV1");

export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> =>
  buildCodecForUnion<MerchantContractTerms>()
    .discriminateOn("version")
    .alternative(undefined, codecForMerchantContractTermsV0())
    .alternative(MerchantContractVersion.V0, codecForMerchantContractTermsV0())
    .alternative(MerchantContractVersion.V1, codecForMerchantContractTermsV1())
    .build("TalerMerchantApi.ContractTerms");

export const codecForMerchantContractChoice =
  (): Codec<MerchantContractChoice> =>
    buildCodecForObject<MerchantContractChoice>()
      .property("amount", codecForAmountString())
      .property("description", codecOptional(codecForString()))
      .property(
        "description_i18n",
        codecOptional(codecForInternationalizedString()),
      )
      .property("inputs", codecForList(codecForMerchantContractInput()))
      .property("outputs", codecForList(codecForMerchantContractOutput()))
      .property("max_fee", codecForAmountString())
      .build("TalerMerchantApi.ContractChoice");

export const codecForMerchantContractInput = (): Codec<MerchantContractInput> =>
  buildCodecForUnion<MerchantContractInput>()
    .discriminateOn("type")
    .alternative(
      MerchantContractInputType.Token,
      codecForMerchantContractInputToken(),
    )
    .build("TalerMerchantApi.ContractInput");

export const codecForMerchantContractInputToken =
  (): Codec<MerchantContractInputToken> =>
    buildCodecForObject<MerchantContractInputToken>()
      .property("type", codecForConstString(MerchantContractInputType.Token))
      .property("token_family_slug", codecForString())
      .property("count", codecOptional(codecForNumber()))
      .build("TalerMerchantApi.ContractInputToken");

export const codecForMerchantContractOutput =
  (): Codec<MerchantContractOutput> =>
    buildCodecForUnion<MerchantContractOutput>()
      .discriminateOn("type")
      .alternative(
        MerchantContractOutputType.Token,
        codecForMerchantContractOutputToken(),
      )
      .alternative(
        MerchantContractOutputType.TaxReceipt,
        codecForMerchantContractOutputTaxReceipt(),
      )
      .build("TalerMerchantApi.ContractOutput");

export const codecForMerchantContractOutputToken =
  (): Codec<MerchantContractOutputToken> =>
    buildCodecForObject<MerchantContractOutputToken>()
      .property("type", codecForConstString(MerchantContractOutputType.Token))
      .property("token_family_slug", codecForString())
      .property("count", codecOptional(codecForNumber()))
      .property("key_index", codecForNumber())
      .build("TalerMerchantApi.ContractOutputToken");

export const codecForMerchantContractOutputTaxReceipt =
  (): Codec<MerchantContractOutputTaxReceipt> =>
    buildCodecForObject<MerchantContractOutputTaxReceipt>()
      .property(
        "type",
        codecForConstString(MerchantContractOutputType.TaxReceipt),
      )
      .property("donau_urls", codecForList(codecForString()))
      .property("amount", codecOptional(codecForAmountString()))
      .build("TalerMerchantApi.ContractOutputTaxReceipt");

export const codecForMerchantContractTokenFamily =
  (): Codec<MerchantContractTokenFamily> =>
    buildCodecForObject<MerchantContractTokenFamily>()
      .property("name", codecForString())
      .property("description", codecForString())
      .property(
        "description_i18n",
        codecOptional(codecForInternationalizedString()),
      )
      .property("keys", codecForList(codecForTokenIssuePublicKey()))
      .property("details", codecForMerchantContractTokenDetails())
      .property("critical", codecForBoolean())
      .build("TalerMerchantApi.ContractTokenFamily");

export const codecForTokenIssuePublicKey = (): Codec<TokenIssuePublicKey> =>
  buildCodecForUnion<TokenIssuePublicKey>()
    .discriminateOn("cipher")
    .alternative("RSA", codecForTokenIssueRsaPublicKey())
    .alternative("CS", codecForTokenIssueCsPublicKey())
    .build("TalerMerchantApi.TokenIssuePublicKey");

export const codecForTokenIssueRsaPublicKey =
  (): Codec<TokenIssueRsaPublicKey> =>
    buildCodecForObject<TokenIssueRsaPublicKey>()
      .property("cipher", codecForConstString("RSA"))
      .property("rsa_pub", codecForString())
      .property("signature_validity_start", codecForTimestamp)
      .property("signature_validity_end", codecForTimestamp)
      .build("TalerMerchantApi.TokenIssueRsaPublicKey");

export const codecForTokenIssueCsPublicKey = (): Codec<TokenIssueCsPublicKey> =>
  buildCodecForObject<TokenIssueCsPublicKey>()
    .property("cipher", codecForConstString("CS"))
    .property("cs_pub", codecForString())
    .property("signature_validity_start", codecForTimestamp)
    .property("signature_validity_end", codecForTimestamp)
    .build("TalerMerchantApi.TokenIssueRsaPublicKey");

export const codecForMerchantContractTokenDetails =
  (): Codec<MerchantContractTokenDetails> =>
    buildCodecForUnion<MerchantContractTokenDetails>()
      .discriminateOn("class")
      .alternative(
        MerchantContractTokenKind.Subscription,
        codecForMerchantContractSubscriptionTokenDetails(),
      )
      .alternative(
        MerchantContractTokenKind.Discount,
        codecForMerchantContractDiscountTokenDetails(),
      )
      .build("TalerMerchantApi.ContractTokenDetails");

export const codecForMerchantContractSubscriptionTokenDetails =
  (): Codec<MerchantContractSubscriptionTokenDetails> =>
    buildCodecForObject<MerchantContractSubscriptionTokenDetails>()
      .property(
        "class",
        codecForConstString(MerchantContractTokenKind.Subscription),
      )
      .property("trusted_domains", codecForList(codecForString()))
      .build("TalerMerchantApi.ContractSubscriptionTokenDetails");

export const codecForMerchantContractDiscountTokenDetails =
  (): Codec<MerchantContractDiscountTokenDetails> =>
    buildCodecForObject<MerchantContractDiscountTokenDetails>()
      .property(
        "class",
        codecForConstString(MerchantContractTokenKind.Discount),
      )
      .property("expected_domains", codecForList(codecForString()))
      .build("TalerMerchantApi.ContractDiscountTokenDetails");

export interface MerchantVersionResponse {
  // libtool-style representation of the Merchant protocol version, see
  // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
  // The format is "current:revision:age".
  version: string;

  // Name of the protocol.
  name: "taler-merchant";

  // URN of the implementation (needed to interpret 'revision' in version).
  // @since **v8**, may become mandatory in the future.
  implementation?: string;

  // Default (!) currency supported by this backend.
  // This is the currency that the backend should
  // suggest by default to the user when entering
  // amounts. See currencies for a list of
  // supported currencies and how to render them.
  currency: string;

  // How services should render currencies supported
  // by this backend.  Maps
  // currency codes (e.g. "EUR" or "KUDOS") to
  // the respective currency specification.
  // All currencies in this map are supported by
  // the backend.  Note that the actual currency
  // specifications are a *hint* for applications
  // that would like *advice* on how to render amounts.
  // Applications *may* ignore the currency specification
  // if they know how to render currencies that they are
  // used with.
  currencies: { [currency: string]: CurrencySpecification };

  // Array of exchanges trusted by the merchant.
  // Since protocol **v6**.
  exchanges: ExchangeConfigInfo[];

  // Set when the merchant supports
  // self-provisioning instances.
  // Since protocol **v21**
  have_self_provisioning?: boolean;

  // True if this merchant backend supports the Donau
  // extension and can thus issue donation receipts.
  // Should primarily be used to control the SPA's CRUD
  // functionality for Donau.
  // @since **v21**
  have_donau: boolean;

  // Tan channels that are required
  // to be confirmed for an instance to
  // be useable.
  // @since **v21**
  mandatory_tan_channels: TanChannel[];

  // Space-separated list of enabled payment target types.
  // Useful if the SPA should not show allow adding other
  // types of bank accounts. "*" is used to represent no
  // restriction.
  // @since **v22**
  payment_target_types: string;

  // Regular expression representing further restrictions
  // on allowed payment targets.  Any "payto://"-URI supplied
  // for a bank account must match the given regular expression.
  // For example, "payto://iban/CH.*" would restrict the system
  // to only Swiss bank accounts.
  // Optional, no restrictions are imposed if the field is
  // absent.
  // @since **v22**
  payment_target_regex: string;

  // Default wire transfer delay for new instances.
  // This is the default to use for new instances, see the instance value for
  // the instance-specific default.
  // @since **v22**
  default_wire_transfer_delay?: RelativeTime;

  // Default payment delay for new instances.
  // This is the default to use for new instances, see the instance value for
  // the instance-specific default.
  // @since **v22**
  default_pay_delay?: RelativeTime;

  // If the frontend does NOT specify a refund deadline, how long should
  // refunds be allowed by default?
  // This is the default to use for new instances, see the instance value for
  // the instance-specific default.
  // @since **v22**
  default_refund_delay?: RelativeTime;
}

export interface ExchangeConfigInfo {
  // Base URL of the exchange REST API.
  base_url: string;

  // Currency for which the merchant is configured
  // to trust the exchange.
  // May not be the one the exchange actually uses,
  // but is the only one we would trust this exchange for.
  currency: string;

  // Offline master public key of the exchange. The
  // /keys data must be signed with this public
  // key for us to trust it.
  master_pub: EddsaPublicKey;
}

export interface ClaimRequest {
  // Nonce to identify the wallet that claimed the order.
  nonce: string;

  // Token that authorizes the wallet to claim the order.
  // *Optional* as the merchant may not have required it
  // (create_token set to false in PostOrderRequest).
  token?: ClaimToken;
}

export interface ClaimResponse {
  // Contract terms of the claimed order
  contract_terms: any;

  // Signature by the merchant over the contract terms.
  sig: EddsaSignatureString;
}

export interface PaymentResponse {
  // Signature on TALER_PaymentResponsePS with the public
  // key of the merchant instance.
  sig: EddsaSignatureString;

  // Text to be shown to the point-of-sale staff as a proof of
  // payment.
  pos_confirmation?: string;
}
export interface PaymentDeniedLegallyResponse {
  // Base URL of the exchanges that denied the payment.
  // The wallet should refresh the coins from these
  // exchanges, but may try to pay with coins from
  // other exchanges.
  exchange_base_urls: string[];
}

export interface PaymentStatusRequestParams {
  // Hash of the order’s contract terms (this is used to
  // authenticate the wallet/customer in case
  // $ORDER_ID is guessable).
  // Required once an order was claimed.
  contractTermHash?: string;
  // Authorizes the request via the claim token that
  // was returned in the PostOrderResponse. Used with
  // unclaimed orders only. Whether token authorization is
  // required is determined by the merchant when the
  // frontend creates the order.
  claimToken?: string;
  // Session ID that the payment must be bound to.
  // If not specified, the payment is not session-bound.
  sessionId?: string;
  // If specified, the merchant backend will wait up to
  // timeout_ms milliseconds for completion of the payment
  // before sending the HTTP response. A client must never
  // rely on this behavior, as the merchant backend may return
  // a response immediately.
  timeout?: number;
  // If set to “yes”, poll for the order’s pending refunds
  // to be picked up. timeout_ms specifies how long we
  // will wait for the refund.
  awaitRefundObtained?: boolean;
  // Indicates that we are polling for a refund above the
  // given AMOUNT. timeout_ms will specify how long we
  // will wait for the refund.
  refund?: AmountString;
  // Since protocol v9 refunded orders are only returned
  // under “already_paid_order_id” if this flag is set
  // explicitly to “YES”.
  allowRefundedForRepurchase?: boolean;
}

export enum KycStatusLongPollingReason {
  AUTH_TRANSFER = 1,
  AML_INVESTIGATION = 2,
  TO_BE_OK = 3,
}
export interface GetKycStatusRequestParams {
  // If specified, the KYC check should return
  // the KYC status only for this wire account.
  // Otherwise, for all wire accounts.
  wireHash?: string;
  //  If specified, the KYC check should return
  // the KYC status only for the given exchange.
  // Otherwise, for all exchanges we interacted with.
  exchangeURL?: string;
  //  If specified, the merchant will wait up to
  // timeout_ms milliseconds for the exchanges to
  // confirm completion of the KYC process(es).
  timeout?: number;

  /**
   * Specifies what status change we are long-polling for.
   * Use 1 to wait for the KYC auth transfer (access token available),
   * 2 to wait for an AML investigation to be done,
   * and 3 to wait for the KYC status to be OK. If multiple accounts
   * or exchanges match the query, any account reaching the TARGET
   * state will cause the response to be returned.
   */
  reason?: KycStatusLongPollingReason;
}
export interface GetOtpDeviceRequestParams {
  // Timestamp in seconds to use when calculating
  // the current OTP code of the device. Since protocol v10.
  faketime?: number;
  // Price to use when calculating the current OTP
  // code of the device. Since protocol v10.
  price?: AmountString;
}
export interface GetOrderRequestParams {
  // Session ID that the payment must be bound to.
  // If not specified, the payment is not session-bound.
  sessionId?: string;
  // Timeout in milliseconds to wait for a payment if
  // the answer would otherwise be negative (long polling).
  timeout?: number;
  // Since protocol v9 refunded orders are only returned
  // under “already_paid_order_id” if this flag is set
  // explicitly to “YES”.
  allowRefundedForRepurchase?: boolean;
}
export interface ListWireTransferRequestParams {
  // Filter for transfers to the given bank account
  // (subject and amount MUST NOT be given in the payto URI).
  paytoURI?: string;
  // Filter for transfers executed before the given timestamp.
  before?: number;
  // Filter for transfers executed after the given timestamp.
  after?: number;
  // At most return the given number of results. Negative for
  // descending in execution time, positive for ascending in
  // execution time. Default is -20.
  limit?: number;
  // Starting transfer_serial_id for an iteration.
  offset?: string;
  // Filter transfers by verification status.
  verified?: boolean;
  order?: "asc" | "dec";
}
export interface ListOrdersRequestParams {
  // If set to yes, only return paid orders, if no only
  // unpaid orders. Do not give (or use “all”) to see all
  // orders regardless of payment status.
  paid?: boolean;
  // If set to yes, only return refunded orders, if no only
  // unrefunded orders. Do not give (or use “all”) to see
  // all orders regardless of refund status.
  refunded?: boolean;
  // If set to yes, only return wired orders, if no only
  // orders with missing wire transfers. Do not give (or
  // use “all”) to see all orders regardless of wire transfer
  // status.
  wired?: boolean;
  // At most return the given number of results. Negative
  // for descending by row ID, positive for ascending by
  // row ID. Default is 20. Since protocol v12.
  limit?: number;
  // Non-negative date in seconds after the UNIX Epoc, see delta
  // for its interpretation. If not specified, we default to the
  // oldest or most recent entry, depending on delta.
  date?: AbsoluteTime;
  // Starting product_serial_id for an iteration.
  // Since protocol v12.
  offset?: string;
  // Timeout in milliseconds to wait for additional orders if the
  // answer would otherwise be negative (long polling). Only useful
  // if delta is positive. Note that the merchant MAY still return
  // a response that contains fewer than delta orders.
  timeout?: number;
  // Since protocol v6. Filters by session ID.
  sessionId?: string;
  // Since protocol v6. Filters by fulfillment URL.
  fulfillmentUrl?: string;

  order?: "asc" | "dec";
}

export interface PayRequest {
  // The coins used to make the payment.
  coins: CoinPaySig[];

  // Custom inputs from the wallet for the contract.
  wallet_data?: Object;

  // The session for which the payment is made (or replayed).
  // Only set for session-based payments.
  session_id?: string;
}

export interface CoinPaySig {
  // Signature by the coin.
  coin_sig: EddsaSignatureString;

  // Public key of the coin being spent.
  coin_pub: EddsaPublicKey;

  // Signature made by the denomination public key.
  ub_sig: RsaSignature;

  // The hash of the denomination public key associated with this coin.
  h_denom: HashCodeString;

  // The amount that is subtracted from this coin with this payment.
  contribution: AmountString;

  // URL of the exchange this coin was withdrawn from.
  exchange_url: string;
}

export interface StatusPaid {
  type: "paid";

  // Was the payment refunded (even partially, via refund or abort)?
  refunded: boolean;

  // Is any amount of the refund still waiting to be picked up (even partially)?
  refund_pending: boolean;

  // Amount that was refunded in total.
  refund_amount: AmountString;

  // Amount that already taken by the wallet.
  refund_taken: AmountString;
}
export interface StatusGotoResponse {
  type: "goto";
  // The client should go to the reorder URL, there a fresh
  // order might be created as this one is taken by another
  // customer or wallet (or repurchase detection logic may
  // apply).
  public_reorder_url: string;
}
export interface StatusUnpaidResponse {
  type: "unpaid";
  // URI that the wallet must process to complete the payment.
  taler_pay_uri: string;

  // Status URL, can be used as a redirect target for the browser
  // to show the order QR code / trigger the wallet.
  fulfillment_url?: string;

  // Alternative order ID which was paid for already in the same session.
  // Only given if the same product was purchased before in the same session.
  already_paid_order_id?: string;
}

export interface PaidRefundStatusResponse {
  // Text to be shown to the point-of-sale staff as a proof of
  // payment (present only if reusable OTP algorithm is used).
  pos_confirmation?: string;

  // True if the order has been subjected to
  // refunds. False if it was simply paid.
  refunded: boolean;
}

export interface PaidRequest {
  // Signature on TALER_PaymentResponsePS with the public
  // key of the merchant instance.
  sig: EddsaSignatureString;

  // Hash of the order's contract terms (this is used to authenticate the
  // wallet/customer and to enable signature verification without
  // database access).
  h_contract: HashCodeString;

  // Hash over custom inputs from the wallet for the contract.
  wallet_data_hash?: HashCodeString;

  // Session id for which the payment is proven.
  session_id: string;
}

export interface AbortRequest {
  // Hash of the order's contract terms (this is used to authenticate the
  // wallet/customer in case $ORDER_ID is guessable).
  h_contract: HashCodeString;

  // List of coins the wallet would like to see refunds for.
  // (Should be limited to the coins for which the original
  // payment succeeded, as far as the wallet knows.)
  coins: AbortingCoin[];
}

export interface AbortResponse {
  // List of refund responses about the coins that the wallet
  // requested an abort for.  In the same order as the coins
  // from the original request.
  // The rtransaction_id is implied to be 0.
  refunds: MerchantAbortPayRefundStatus[];
}

export interface WalletRefundRequest {
  // Hash of the order's contract terms (this is used to authenticate the
  // wallet/customer).
  h_contract: HashCodeString;
}

export interface WalletRefundResponse {
  // Amount that was refunded in total.
  refund_amount: AmountString;

  // Successful refunds for this payment, empty array for none.
  refunds: MerchantCoinRefundStatus[];

  // Public key of the merchant.
  merchant_pub: EddsaPublicKey;
}

// Additional details needed to verify the refund confirmation signature
// (h_contract_terms and merchant_pub) are already known
// to the wallet and thus not included.
export interface MerchantCoinRefundSuccessStatus {
  // Used as tag for the sum type MerchantCoinRefundStatus sum type.
  type: "success";

  // HTTP status of the exchange request, 200 (integer) required for refund confirmations.
  exchange_status: 200;

  // The EdDSA :ref:signature (binary-only) with purpose
  // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the
  // exchange affirming the successful refund.
  exchange_sig: EddsaSignatureString;

  // Public EdDSA key of the exchange that was used to generate the signature.
  // Should match one of the exchange's signing keys from /keys.  It is given
  // explicitly as the client might otherwise be confused by clock skew as to
  // which signing key was used.
  exchange_pub: EddsaPublicKey;

  // Refund transaction ID.
  rtransaction_id: Integer;

  // Public key of a coin that was refunded.
  coin_pub: EddsaPublicKey;

  // Amount that was refunded, including refund fee charged by the exchange
  // to the customer.
  refund_amount: AmountString;

  // Timestamp when the merchant approved the refund.
  // Useful for grouping refunds.
  execution_time: Timestamp;
}

export interface InstanceConfigurationMessage {
  // Name of the merchant instance to create (will become $INSTANCE).
  // Must match the regex ^[A-Za-z0-9][A-Za-z0-9_.@-]+$.
  id: string;

  // Merchant name corresponding to this instance.
  name: string;

  // Merchant email for customer contact.
  email?: string;

  // Merchant phone number for password reset (2-FA)
  // @since **v21**.
  phone_number?: string;

  // Merchant public website.
  website?: string;

  // Merchant logo.
  logo?: ImageDataUrl;

  // Authentication settings for this instance
  auth: InstanceAuthConfigurationMessage;

  // The merchant's physical address (to be put into contracts).
  address: Location;

  // The jurisdiction under which the merchant conducts its business
  // (to be put into contracts).
  jurisdiction: Location;

  // Use STEFAN curves to determine default fees?
  // If false, no fees are allowed by default.
  // Can always be overridden by the frontend on a per-order basis.
  use_stefan: boolean;

  // If the frontend does NOT specify an execution date, how long should
  // we tell the exchange to wait to aggregate transactions before
  // executing the wire transfer?  This delay is added to the current
  // time when we generate the advisory execution time for the exchange.
  default_wire_transfer_delay: RelativeTime;

  // If the frontend does NOT specify a payment deadline, how long should
  // offers we make be valid by default?
  default_pay_delay: RelativeTime;
}

export interface InstanceAuthConfigurationMessage {
  // Type of authentication.
  // "external":  The mechant backend does not do
  //   any authentication checks.  Instead an API
  //   gateway must do the authentication.
  // "token": (deprecated) The merchant checks an auth token.
  //   See "token" for details.
  //   Since **v19**: APIs use login tokens retrieved from the /private/token
  //   endpoint.
  //   See "password" for details.
  method: MerchantAuthMethod;

  // Deprecated: For method "token", this field is mandatory.
  // The token MUST begin with the string "secret-token:".
  // After the auth token has been set (with method "token"),
  // the value must be provided in a "Authorization: Bearer $token"
  // header.
  // token?: AccessToken;

  // Since **v19**: For method "token", this field is mandatory.
  // Authentication against the /private/token endpoint
  // is done using basic authentication with the configured password
  // in the "password" field. Tokens are passed to other endpoints for
  // authorization using RFC 8959 bearer tokens.
  password: string;
}

export enum LoginTokenScope {
  ReadOnly = "readonly",
  All = "all",
  Spa = "spa",
  OrderSimple = "order-simple",
  OrderPos = "order-pos",
  OrderManagement = "order-mgmt",
  OrderFull = "order-full",
  ReadOnly_Refreshable = "readonly:refreshable",
  All_Refreshable = "all:refreshable",
  Spa_Refreshable = "spa:refreshable",
  OrderSimple_Refreshable = "order-simple:refreshable",
  OrderPos_Refreshable = "order-pos:refreshable",
  OrderManagement_Refreshable = "order-mgmt:refreshable",
  OrderFull_Refreshable = "order-full:refreshable",
}

export interface LoginTokenRequest {
  // Scope of the token (which kinds of operations it will allow)
  scope: LoginTokenScope;

  // Server may impose its own upper bound
  // on the token validity duration
  duration?: RelativeTime;

  // Optional token description
  description?: string;

  // Can this token be refreshed?
  // Defaults to false. Deprecated since **v19**.
  // Use ":refreshable" scope prefix instead.
  // refreshable?: boolean;
}

export interface LoginTokenSuccessResponse {
  // @deprecated since v19. See access_token
  // token: string;

  // The login token that can be used to access resources
  // that are in scope for some time. Must be prefixed
  // with "Bearer " when used in the "Authorization" HTTP header.
  // Will already begin with the RFC 8959 prefix.
  // **Since v19**
  access_token: AccessToken;

  // Scope of the token (which kinds of operations it will allow)
  scope: LoginTokenScope;

  // Server may impose its own upper bound
  // on the token validity duration
  expiration: Timestamp;

  // Can this token be refreshed?
  refreshable: boolean;
}

export interface InstanceReconfigurationMessage {
  // Merchant name corresponding to this instance.
  name: string;

  // Merchant email for customer contact.
  email?: string;

  // Merchant phone number for password reset (2-FA)
  // @since **v21**.
  phone_number?: string;

  // Merchant public website.
  website?: string;

  // Merchant logo.
  logo?: ImageDataUrl;

  // The merchant's physical address (to be put into contracts).
  address: Location;

  // The jurisdiction under which the merchant conducts its business
  // (to be put into contracts).
  jurisdiction: Location;

  // Use STEFAN curves to determine default fees?
  // If false, no fees are allowed by default.
  // Can always be overridden by the frontend on a per-order basis.
  use_stefan: boolean;

  // If the frontend does NOT specify an execution date, how long should
  // we tell the exchange to wait to aggregate transactions before
  // executing the wire transfer?  This delay is added to the current
  // time when we generate the advisory execution time for the exchange.
  default_wire_transfer_delay: RelativeTime;

  // If the frontend does NOT specify a payment deadline, how long should
  // offers we make be valid by default?
  default_pay_delay: RelativeTime;
}

export interface InstancesResponse {
  // List of instances that are present in the backend (see Instance).
  instances: Instance[];
}

export interface Instance {
  // Merchant name corresponding to this instance.
  name: string;

  // Merchant public website.
  website?: string;

  // Merchant logo.
  logo?: ImageDataUrl;

  // Merchant instance this response is about ($INSTANCE).
  id: string;

  // Public key of the merchant/instance, in Crockford Base32 encoding.
  merchant_pub: EddsaPublicKey;

  // List of the payment targets supported by this instance. Clients can
  // specify the desired payment target in /order requests.  Note that
  // front-ends do not have to support wallets selecting payment targets.
  payment_targets: string[];

  // Has this instance been deleted (but not purged)?
  deleted: boolean;
}

export interface QueryInstancesResponse {
  // Merchant name corresponding to this instance.
  name: string;

  // Merchant email for customer contact.
  email?: string;

  // True if the email address was validated.
  // @since **v21**.
  email_validated?: boolean;

  // Merchant phone number for password reset (2-FA)
  // @since **v21**.
  phone_number?: string;

  // True if the email address was validated.
  // @since **v21**.
  phone_validated?: boolean;

  // Merchant public website.
  website?: string;

  // Merchant logo.
  logo?: ImageDataUrl;

  // Public key of the merchant/instance, in Crockford Base32 encoding.
  merchant_pub: EddsaPublicKey;

  // The merchant's physical address (to be put into contracts).
  address: Location;

  // The jurisdiction under which the merchant conducts its business
  // (to be put into contracts).
  jurisdiction: Location;

  // Use STEFAN curves to determine default fees?
  // If false, no fees are allowed by default.
  // Can always be overridden by the frontend on a per-order basis.
  use_stefan: boolean;

  // If the frontend does NOT specify an execution date, how long should
  // we tell the exchange to wait to aggregate transactions before
  // executing the wire transfer?  This delay is added to the current
  // time when we generate the advisory execution time for the exchange.
  default_wire_transfer_delay: RelativeTime;

  // If the frontend does NOT specify a payment deadline, how long should
  // offers we make be valid by default?
  default_pay_delay: RelativeTime;

  // Authentication configuration.
  // Does not contain the token when token auth is configured.
  auth: {
    method: MerchantAuthMethod;
  };
}

// Type of authentication.
// "external":  The mechant backend does not do
//   any authentication checks.  Instead an API
//   gateway must do the authentication.
// "token": The merchant checks an auth token.
//   See "token" for details.
export enum MerchantAuthMethod {
  TOKEN = "token",
  // EXTERNAL = "external",
  // PASSWORD = "password",
}

export interface MerchantAccountKycRedirectsResponse {
  // Array of KYC status information for
  // the exchanges and bank accounts selected
  // by the query.
  kyc_data: MerchantAccountKycRedirect[];
}

export enum MerchantAccountKycStatus {
  NO_EXCHANGE_KEY = "no-exchange-keys",
  KYC_WIRE_IMPOSSIBLE = "kyc-wire-impossible",
  KYC_WIRE_REQUIRED = "kyc-wire-required",
  KYC_REQUIRED = "kyc-required",
  AWAITING_AML_REVIEW = "awaiting-aml-review",
  READY = "ready",
  LOGIC_BUG = "logic-bug",
  EXCHANGE_INTERNAL_ERROR = "exchange-internal-error",
  EXCHANGE_GATEWAY_TIMEOUT = "exchange-gateway-timeout",
  EXCHANGE_UNREACHABLE = "exchange-unreachable",
  EXCHANGE_STATUS_INVALID = "exchange-status-invalid",
}

/**
 * Higher is more important
 */
export enum MerchantAccountKycStatusSimplified {
  OK = 0,
  ACTION_REQUIRED = 100,
  WARNING = 200,
  ERROR = 300,
}

export function getMerchantAccountKycStatusSimplified(
  st: MerchantAccountKycStatus,
): MerchantAccountKycStatusSimplified {
  switch (st) {
    case MerchantAccountKycStatus.AWAITING_AML_REVIEW:
    case MerchantAccountKycStatus.READY:
      return MerchantAccountKycStatusSimplified.OK;
    case MerchantAccountKycStatus.KYC_WIRE_REQUIRED:
    case MerchantAccountKycStatus.KYC_REQUIRED:
      return MerchantAccountKycStatusSimplified.ACTION_REQUIRED;
    case MerchantAccountKycStatus.NO_EXCHANGE_KEY:
    case MerchantAccountKycStatus.EXCHANGE_INTERNAL_ERROR:
    case MerchantAccountKycStatus.EXCHANGE_GATEWAY_TIMEOUT:
    case MerchantAccountKycStatus.EXCHANGE_UNREACHABLE:
      return MerchantAccountKycStatusSimplified.WARNING;
    case MerchantAccountKycStatus.KYC_WIRE_IMPOSSIBLE:
    case MerchantAccountKycStatus.LOGIC_BUG:
    case MerchantAccountKycStatus.EXCHANGE_STATUS_INVALID:
      return MerchantAccountKycStatusSimplified.ERROR;
    default:
      assertUnreachable(st);
  }
}

export interface MerchantAccountKycRedirect {
  // Summary of the status of the KYC process. Possible values are:
  //
  // o "no-exchange-keys": we do not (yet) have the /keys of the exchange
  // - "kyc-wire-impossible": KYC auth transfer needed but not possible
  // @ "kyc-wire-required": KYC auth transfer still needed and possible
  // @ "kyc-required": merchant must supply KYC data to proceed
  // + "awaiting-aml-review": account under review by payment provider
  // + "ready": everything is fine, account can be fully used
  // - "logic-bug": merchant backend logic bug
  // o "exchange-internal-error": exchange had an internal error
  // o "exchange-gateway-timeout": network timeout at gateway
  // o "exchange-unreachable": exchange did not respond at all
  // - "exchange-status-invalid": exchange violated protocol in reply
  //
  // "+" are perfectly normal states, "@" are states where the user
  // must performn an action (show link!); "o" are reasonable transient
  // states that could happen and are we are expected to likely recover
  // from automatically but that we should inform the user about
  // (show in yellow?), "-" are hard error states from which
  // there is likely no good automatic recovery from (show in red?).
  status: MerchantAccountKycStatus;

  // Our bank wire account this is about.
  payto_uri: PaytoString;

  // Hash of the salted payto://-URI of our
  // bank wire account this is about.
  // Since protocol **v17**.
  h_wire: string;

  // Base URL of the exchange this is about.
  exchange_url: string;

  // HTTP status code returned by the exchange when we asked for
  // information about the KYC status.
  // Since protocol **v17**.
  exchange_http_status: number;

  // Set to true if we did not get a /keys response from
  // the exchange and thus cannot do certain checks, such as
  // determining default account limits or account eligibility.
  no_keys: boolean;

  // Set to true if the given account cannot to KYC at the
  // given exchange because no wire method exists that could
  // be used to do the KYC auth wire transfer.
  auth_conflict: boolean;

  // Numeric error code indicating errors the exchange
  // returned, or TALER_EC_INVALID for none.
  // Optional (as there may not always have
  // been an error code). Since protocol **v17**.
  exchange_code?: number;

  // Access token needed to open the KYC SPA and/or
  // access the /kyc-info/ endpoint.
  access_token?: AccessToken;

  // Array with limitations that currently apply to this
  // account and that may be increased or lifted if the
  // KYC check is passed.
  // Note that additional limits *may* exist and not be
  // communicated to the client. If such limits are
  // reached, this *may* be indicated by the account
  // going into aml_review state. However, it is
  // also possible that the exchange may legally have
  // to deny operations without being allowed to provide
  // any justification.
  // The limits should be used by the client to
  // possibly structure their operations (e.g. withdraw
  // what is possible below the limit, ask the user to
  // pass KYC checks or withdraw the rest after the time
  // limit is passed, warn the user to not withdraw too
  // much or even prevent the user from generating a
  // request that would cause it to exceed hard limits).
  limits?: AccountLimit[];

  // Array of wire transfer instructions (including
  // optional amount and subject) for a KYC auth wire
  // transfer. Set only if this is required
  // to get the given exchange working.
  // Array because the exchange may have multiple
  // bank accounts, in which case any of these
  // accounts will do.
  // Optional. Since protocol **v17**.
  payto_kycauths?: string[];
}

export interface ExchangeKycTimeout {
  // Base URL of the exchange this is about.
  exchange_url: string;

  // Numeric error code indicating errors the exchange
  // returned, or TALER_EC_INVALID for none.
  exchange_code: number;

  // HTTP status code returned by the exchange when we asked for
  // information about the KYC status.
  // 0 if there was no response at all.
  exchange_http_status: number;
}

export interface AccountAddDetails {
  // payto:// URI of the account.
  payto_uri: PaytoString;

  // URL from where the merchant can download information
  // about incoming wire transfers to this account.
  credit_facade_url?: string;

  // Credentials to use when accessing the credit facade.
  // Never returned on a GET (as this may be somewhat
  // sensitive data). Can be set in POST
  // or PATCH requests to update (or delete) credentials.
  // To really delete credentials, set them to the type: "none".
  credit_facade_credentials?: FacadeCredentials;
}

export type FacadeCredentials =
  | NoFacadeCredentials
  | BasicAuthFacadeCredentials
  | BearerAuthFacadeCredentials;

export interface NoFacadeCredentials {
  type: "none";
}

export interface BasicAuthFacadeCredentials {
  type: "basic";

  // Username to use to authenticate
  username: string;

  // Password to use to authenticate
  password: string;
}

export interface BearerAuthFacadeCredentials {
  type: "bearer";

  // token to use to authenticate
  token: string;
}

export interface AccountAddResponse {
  // Hash over the wire details (including over the salt).
  h_wire: HashCode;

  // Salt used to compute h_wire.
  salt: HashCode;
}

export interface AccountPatchDetails {
  // URL from where the merchant can download information
  // about incoming wire transfers to this account.
  credit_facade_url?: string;

  // Credentials to use when accessing the credit facade.
  // Never returned on a GET (as this may be somewhat
  // sensitive data). Can be set in POST
  // or PATCH requests to update (or delete) credentials.
  // To really delete credentials, set them to the type: "none".
  // If the argument is omitted, the old credentials
  // are simply preserved.
  credit_facade_credentials?: FacadeCredentials;
}

export interface AccountsSummaryResponse {
  // List of accounts that are known for the instance.
  accounts: BankAccountEntry[];
}

// TODO: missing in docs
export interface BankAccountEntry {
  // payto:// URI of the account.
  payto_uri: PaytoString;

  // Hash over the wire details (including over the salt).
  h_wire: HashCode;

  // true if this account is active,
  // false if it is historic.
  active?: boolean;
}
export interface BankAccountDetail {
  // payto:// URI of the account.
  payto_uri: PaytoString;

  // Hash over the wire details (including over the salt).
  h_wire: HashCode;

  // Salt used to compute h_wire.
  salt: HashCode;

  // URL from where the merchant can download information
  // about incoming wire transfers to this account.
  credit_facade_url?: string;

  // true if this account is active,
  // false if it is historic.
  active?: boolean;
}

export interface CategoryListResponse {
  // Array with all of the categories we know.
  categories: CategoryListEntry[];
}

export interface CategoryListEntry {
  // Unique number for the category.
  category_id: Integer;

  // Name of the category.
  name: string;

  // Translations of the name into various
  // languages.
  name_i18n?: { [lang_tag: string]: string };

  // Number of products in this category.
  // A product can be in more than one category.
  product_count: Integer;
}

export interface CategoryProductList {
  // Name of the category.
  name: string;

  // Translations of the name into various
  // languages.
  name_i18n?: { [lang_tag: string]: string };

  // The products in this category.
  products: ProductSummary[];
}

export interface ProductSummary {
  // Product ID to use.
  product_id: string;
}

export interface CategoryCreateRequest {
  // Name of the category.
  name: string;

  // Translations of the name into various
  // languages.
  name_i18n?: { [lang_tag: string]: string };
}

export interface CategoryCreatedResponse {
  // Number of the newly created category.
  category_id: Integer;
}

export interface ProductAddDetail {
  // Product ID to use.
  product_id: string;

  // Human-readable product name.
  // Since API version **v20**.  Optional only for
  // backwards-compatibility, should be considered mandatory
  // moving forward!
  product_name?: string;

  // Human-readable product description.
  description: string;

  // Map from IETF BCP 47 language tags to localized descriptions.
  description_i18n?: { [lang_tag: string]: string };

  // Categories into which the product belongs.
  // Used in the POS-endpoint.
  // Since API version **v16**.
  categories?: Integer[];

  // Unit in which the product is measured (liters, kilograms, packages, etc.).
  unit: string;

  // The price for one unit of the product. Zero is used
  // to imply that this product is not sold separately, or
  // that the price is not fixed, and must be supplied by the
  // front-end.  If non-zero, this price MUST include applicable
  // taxes.
  price: AmountString;

  // An optional base64-encoded product image.
  image?: ImageDataUrl;

  // A list of taxes paid by the merchant for one unit of this product.
  taxes?: Tax[];

  // Number of units of the product in stock in sum in total,
  // including all existing sales ever. Given in product-specific
  // units.
  // A value of -1 indicates "infinite" (i.e. for "electronic" books).
  total_stock: Integer;

  // Identifies where the product is in stock.
  address?: Location;

  // Identifies when we expect the next restocking to happen.
  next_restock?: Timestamp;

  // Minimum age buyer must have (in years). Default is 0.
  minimum_age?: Integer;
}

export interface ProductPatchDetail {
  // Human-readable product name.
  // Since API version **v20**.  Optional only for
  // backwards-compatibility, should be considered mandatory
  // moving forward!
  product_name?: string;

  // Human-readable product description.
  description: string;

  // Map from IETF BCP 47 language tags to localized descriptions.
  description_i18n?: { [lang_tag: string]: string };

  // Categories into which the product belongs.
  // Used in the POS-endpoint.
  // Since API version **v16**.
  categories?: Integer[];

  // Unit in which the product is measured (liters, kilograms, packages, etc.).
  unit: string;

  // The price for one unit of the product. Zero is used
  // to imply that this product is not sold separately, or
  // that the price is not fixed, and must be supplied by the
  // front-end.  If non-zero, this price MUST include applicable
  // taxes.
  price: AmountString;

  // An optional base64-encoded product image.
  image?: ImageDataUrl;

  // A list of taxes paid by the merchant for one unit of this product.
  taxes?: Tax[];

  // Number of units of the product in stock in sum in total,
  // including all existing sales ever. Given in product-specific
  // units.
  // A value of -1 indicates "infinite" (i.e. for "electronic" books).
  total_stock: Integer;

  // Number of units of the product that were lost (spoiled, stolen, etc.).
  total_lost?: Integer;

  // Identifies where the product is in stock.
  address?: Location;

  // Identifies when we expect the next restocking to happen.
  next_restock?: Timestamp;

  // Minimum age buyer must have (in years). Default is 0.
  minimum_age?: Integer;
}

export interface InventorySummaryResponse {
  // List of products that are present in the inventory.
  products: InventoryEntry[];
}

export interface InventoryEntry {
  // Product identifier, as found in the product.
  product_id: string;
  // product_serial_id of the product in the database.
  product_serial: Integer;
}

export interface FullInventoryDetailsResponse {
  // List of products that are present in the inventory.
  products: MerchantPosProductDetail[];

  // List of categories in the inventory.
  categories: MerchantCategory[];
}

export interface MerchantPosProductDetail {
  // A unique numeric ID of the product
  product_serial: number;

  // A merchant-internal unique identifier for the product
  product_id?: string;

  // Human-readable product name.
  // Since API version **v20**.
  product_name?: string;

  // A list of category IDs this product belongs to.
  // Typically, a product only belongs to one category, but more than one is supported.
  categories: number[];

  // Human-readable product description.
  description: string;

  // Map from IETF BCP 47 language tags to localized descriptions.
  description_i18n: { [lang_tag: string]: string };

  // Unit in which the product is measured (liters, kilograms, packages, etc.).
  unit: string;

  // The price for one unit of the product. Zero is used
  // to imply that this product is not sold separately, or
  // that the price is not fixed, and must be supplied by the
  // front-end.  If non-zero, this price MUST include applicable
  // taxes.
  price: AmountString;

  // An optional base64-encoded product image.
  image?: ImageDataUrl;

  // A list of taxes paid by the merchant for one unit of this product.
  taxes?: Tax[];

  // Number of units of the product in stock in sum in total,
  // including all existing sales ever. Given in product-specific
  // units.
  // Optional, if missing treat as "infinite".
  total_stock?: Integer;

  // Minimum age buyer must have (in years).
  minimum_age?: Integer;
}

export interface MerchantCategory {
  // A unique numeric ID of the category
  id: number;

  // The name of the category. This will be shown to users and used in the order summary.
  name: string;

  // Map from IETF BCP 47 language tags to localized names
  name_i18n?: { [lang_tag: string]: string };
}

export interface ProductDetail {
  // Human-readable product name.
  // Since API version **v20**.
  product_name?: string;

  // Human-readable product description.
  description: string;

  // Map from IETF BCP 47 language tags to localized descriptions.
  description_i18n: { [lang_tag: string]: string };

  // Unit in which the product is measured (liters, kilograms, packages, etc.).
  unit: string;

  // Categories into which the product belongs.
  // Since API version **v16**.
  categories: Integer[];

  // The price for one unit of the product. Zero is used
  // to imply that this product is not sold separately, or
  // that the price is not fixed, and must be supplied by the
  // front-end.  If non-zero, this price MUST include applicable
  // taxes.
  price: AmountString;

  // An optional base64-encoded product image.
  image: ImageDataUrl;

  // A list of taxes paid by the merchant for one unit of this product.
  taxes?: Tax[];

  // Number of units of the product in stock in sum in total,
  // including all existing sales ever. Given in product-specific
  // units.
  // A value of -1 indicates "infinite" (i.e. for "electronic" books).
  total_stock: Integer;

  // Number of units of the product that have already been sold.
  total_sold: Integer;

  // Number of units of the product that were lost (spoiled, stolen, etc.).
  total_lost: Integer;

  // Identifies where the product is in stock.
  address?: Location;

  // Identifies when we expect the next restocking to happen.
  next_restock?: Timestamp;

  // Minimum age buyer must have (in years).
  minimum_age?: Integer;
}
export interface LockRequest {
  // UUID that identifies the frontend performing the lock
  // Must be unique for the lifetime of the lock.
  lock_uuid: string;

  // How long does the frontend intend to hold the lock?
  duration: RelativeTime;

  // How many units should be locked?
  quantity: Integer;
}

export interface PostOrderRequest {
  // The order must at least contain the minimal
  // order detail, but can override all.
  order: Order;

  // If set, the backend will then set the refund deadline to the current
  // time plus the specified delay.  If it's not set, refunds will not be
  // possible.
  refund_delay?: RelativeTime;

  // Specifies the payment target preferred by the client. Can be used
  // to select among the various (active) wire methods supported by the instance.
  payment_target?: string;

  // Specifies that some products are to be included in the
  // order from the inventory.  For these inventory management
  // is performed (so the products must be in stock) and
  // details are completed from the product data of the backend.
  inventory_products?: MinimalInventoryProduct[];

  // Specifies a lock identifier that was used to
  // lock a product in the inventory.  Only useful if
  // inventory_products is set.  Used in case a frontend
  // reserved quantities of the individual products while
  // the shopping cart was being built.  Multiple UUIDs can
  // be used in case different UUIDs were used for different
  // products (i.e. in case the user started with multiple
  // shopping sessions that were combined during checkout).
  lock_uuids?: string[];

  // Should a token for claiming the order be generated?
  // False can make sense if the ORDER_ID is sufficiently
  // high entropy to prevent adversarial claims (like it is
  // if the backend auto-generates one). Default is 'true'.
  create_token?: boolean;

  // OTP device ID to associate with the order.
  // This parameter is optional.
  otp_id?: string;
}

export type Order = OrderV0 | OrderV1;

export interface MinimalOrderDetail {
  // Amount to be paid by the customer.
  amount: AmountString;

  // Short summary of the order.
  summary: string;

  // See documentation of fulfillment_url in ContractTerms.
  // Either fulfillment_url or fulfillment_message must be specified.
  // When creating an order, the fulfillment URL can
  // contain ${ORDER_ID} which will be substituted with the
  // order ID of the newly created order.
  fulfillment_url?: string;

  // See documentation of fulfillment_message in ContractTerms.
  // Either fulfillment_url or fulfillment_message must be specified.
  fulfillment_message?: string;
}

export interface MinimalInventoryProduct {
  // Which product is requested (here mandatory!).
  product_id: string;

  // How many units of the product are requested.
  quantity: Integer;
}

export interface PostOrderResponse {
  // Order ID of the response that was just created.
  order_id: string;

  // Deadline when the offer expires; the customer must pay before.
  // @since protocol **v21**.
  pay_deadline?: Timestamp;

  // Token that authorizes the wallet to claim the order.
  // Provided only if "create_token" was set to 'true'
  // in the request.
  token?: ClaimToken;
}
export interface OutOfStockResponse {
  // Product ID of an out-of-stock item.
  product_id: string;

  // Requested quantity.
  requested_quantity: Integer;

  // Available quantity (must be below requested_quantity).
  available_quantity: Integer;

  // When do we expect the product to be again in stock?
  // Optional, not given if unknown.
  restock_expected?: Timestamp;
}

export interface OrderHistory {
  // Timestamp-sorted array of all orders matching the query.
  // The order of the sorting depends on the sign of delta.
  orders: OrderHistoryEntry[];
}

export interface OrderHistoryEntry {
  // Order ID of the transaction related to this entry.
  order_id: string;

  // Row ID of the order in the database.
  row_id: number;

  // When the order was created.
  timestamp: Timestamp;

  // The amount of money the order is for.
  amount: AmountString;

  // The summary of the order.
  summary: string;

  // Whether some part of the order is refundable,
  // that is the refund deadline has not yet expired
  // and the total amount refunded so far is below
  // the value of the original transaction.
  refundable: boolean;

  // Whether the order has been paid or not.
  paid: boolean;
}

export type MerchantOrderStatusResponse =
  | CheckPaymentPaidResponse
  | CheckPaymentClaimedResponse
  | CheckPaymentUnpaidResponse;

export interface CheckPaymentPaidResponse {
  // The customer paid for this contract.
  order_status: "paid";

  // Was the payment refunded (even partially)?
  refunded: boolean;

  // True if there are any approved refunds that the wallet has
  // not yet obtained.
  refund_pending: boolean;

  // Did the exchange wire us the funds?
  wired: boolean;

  // Total amount the exchange deposited into our bank account
  // for this contract, excluding fees.
  deposit_total: AmountString;

  // Numeric error code indicating errors the exchange
  // encountered tracking the wire transfer for this purchase (before
  // we even got to specific coin issues).
  // 0 if there were no issues.
  exchange_code: number;

  // HTTP status code returned by the exchange when we asked for
  // information to track the wire transfer for this purchase.
  // 0 if there were no issues.
  exchange_http_status: number;

  // Total amount that was refunded, 0 if refunded is false.
  refund_amount: AmountString;

  // Contract terms.
  // FIXME: support for contract v1
  contract_terms: MerchantContractTerms;

  // The wire transfer status from the exchange for this order if
  // available, otherwise empty array.
  wire_details: TransactionWireTransfer[];

  // Reports about trouble obtaining wire transfer details,
  // empty array if no trouble were encountered.
  wire_reports: TransactionWireReport[];

  // The refund details for this order.  One entry per
  // refunded coin; empty array if there are no refunds.
  refund_details: RefundDetails[];

  // Status URL, can be used as a redirect target for the browser
  // to show the order QR code / trigger the wallet.
  order_status_url: string;
}

export interface CheckPaymentClaimedResponse {
  // A wallet claimed the order, but did not yet pay for the contract.
  order_status: "claimed";

  // Contract terms.
  contract_terms: MerchantContractTerms;

  // Status URL, can be used as a redirect target for the browser
  // to show the order QR code / trigger the wallet.
  order_status_url: string;
}

export interface CheckPaymentUnpaidResponse {
  // The order was neither claimed nor paid.
  order_status: "unpaid";

  // URI that the wallet must process to complete the payment.
  taler_pay_uri: string;

  // Time when the order was created.
  creation_time: Timestamp;

  // Deadline when the offer expires; the customer must pay before.
  // @since protocol **v21**.
  pay_deadline: Timestamp | undefined;

  // Order summary text.
  summary: string;

  // Total amount of the order (to be paid by the customer).
  total_amount: AmountString;

  // Alternative order ID which was paid for already in the same session.
  // Only given if the same product was purchased before in the same session.
  already_paid_order_id?: string;

  // Fulfillment URL of an already paid order. Only given if under this
  // session an already paid order with a fulfillment URL exists.
  already_paid_fulfillment_url?: string;

  // Status URL, can be used as a redirect target for the browser
  // to show the order QR code / trigger the wallet.
  order_status_url: string;

  // We do we NOT return the contract terms here because they may not
  // exist in case the wallet did not yet claim them.
}
export interface RefundDetails {
  // Reason given for the refund.
  reason: string;

  // Set to true if a refund is still available for the wallet for this payment.
  pending: boolean;

  // When was the refund approved.
  timestamp: Timestamp;

  // Total amount that was refunded (minus a refund fee).
  amount: AmountString;
}

export interface TransactionWireTransfer {
  // Responsible exchange.
  exchange_url: string;

  // 32-byte wire transfer identifier.
  wtid: Base32String;

  // Execution time of the wire transfer.
  execution_time: Timestamp;

  // Total amount that has been wire transferred
  // to the merchant.
  amount: AmountString;

  // Was this transfer confirmed by the merchant via the
  // POST /transfers API, or is it merely claimed by the exchange?
  confirmed: boolean;
}

export interface TransactionWireReport {
  // Numerical error code.
  code: number;

  // Human-readable error description.
  hint: string;

  // Numerical error code from the exchange.
  exchange_code: number;

  // HTTP status code received from the exchange.
  exchange_http_status: number;

  // Public key of the coin for which we got the exchange error.
  coin_pub: CoinPublicKey;
}

export interface ForgetRequest {
  // Array of valid JSON paths to forgettable fields in the order's
  // contract terms.
  fields: string[];
}

export interface RefundRequest {
  // Amount to be refunded.
  refund: AmountString;

  // Human-readable refund justification.
  reason: string;
}
export interface MerchantRefundResponse {
  // URL (handled by the backend) that the wallet should access to
  // trigger refund processing.
  // taler://refund/...
  taler_refund_uri: string;

  // Contract hash that a client may need to authenticate an
  // HTTP request to obtain the above URI in a wallet-friendly way.
  h_contract: HashCode;
}

export interface TransferInformation {
  // How much was wired to the merchant (minus fees).
  credit_amount: AmountString;

  // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value).
  wtid: WireTransferIdentifierRawP;

  // Target account that received the wire transfer.
  payto_uri: PaytoString;

  // Base URL of the exchange that made the wire transfer.
  exchange_url: string;
}

export interface TransferList {
  // List of all the transfers that fit the filter that we know.
  transfers: TransferDetails[];
}
export interface TransferDetails {
  // How much was wired to the merchant (minus fees).
  credit_amount: AmountString;

  // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value).
  wtid: WireTransferIdentifierRawP;

  // Target account that received the wire transfer.
  payto_uri: PaytoString;

  // Base URL of the exchange that made the wire transfer.
  exchange_url: string;

  // Serial number identifying the transfer in the merchant backend.
  // Used for filtering via offset.
  transfer_serial_id: number;

  // Time of the execution of the wire transfer by the exchange, according to the exchange
  // Only provided if we did get an answer from the exchange.
  execution_time?: Timestamp;

  // True if we checked the exchange's answer and are happy with it.
  // False if we have an answer and are unhappy, missing if we
  // do not have an answer from the exchange.
  verified?: boolean;

  // True if the merchant uses the POST /transfers API to confirm
  // that this wire transfer took place (and it is thus not
  // something merely claimed by the exchange).
  confirmed?: boolean;
}

export interface OtpDeviceAddDetails {
  // Device ID to use.
  otp_device_id: string;

  // Human-readable description for the device.
  otp_device_description: string;

  // A key encoded with RFC 3548 Base32.
  // IMPORTANT: This is not using the typical
  // Taler base32-crockford encoding.
  // Instead it uses the RFC 3548 encoding to
  // be compatible with the TOTP standard.
  otp_key: string;

  // Algorithm for computing the POS confirmation.
  // "NONE" or 0: No algorithm (no pos confirmation will be generated)
  // "TOTP_WITHOUT_PRICE" or 1: Without amounts (typical OTP device)
  // "TOTP_WITH_PRICE" or 2: With amounts (special-purpose OTP device)
  // The "String" variants are supported @since protocol **v7**.
  otp_algorithm: Integer | string;

  // Counter for counter-based OTP devices.
  otp_ctr?: Integer;
}

export interface OtpDevicePatchDetails {
  // Human-readable description for the device.
  otp_device_description: string;

  // A key encoded with RFC 3548 Base32.
  // IMPORTANT: This is not using the typical
  // Taler base32-crockford encoding.
  // Instead it uses the RFC 3548 encoding to
  // be compatible with the TOTP standard.
  otp_key: string;

  // Algorithm for computing the POS confirmation.
  otp_algorithm: Integer;

  // Counter for counter-based OTP devices.
  otp_ctr?: Integer;
}

export interface OtpDeviceSummaryResponse {
  // Array of devices that are present in our backend.
  otp_devices: OtpDeviceEntry[];
}
export interface OtpDeviceEntry {
  // Device identifier.
  otp_device_id: string;

  // Human-readable description for the device.
  device_description: string;
}

export interface OtpDeviceDetails {
  // Human-readable description for the device.
  device_description: string;

  // Algorithm for computing the POS confirmation.
  //
  // Currently, the following numbers are defined:
  // 0: None
  // 1: TOTP without price
  // 2: TOTP with price
  otp_algorithm: Integer;

  // Counter for counter-based OTP devices.
  otp_ctr?: Integer;

  // Current time for time-based OTP devices.
  // Will match the faketime argument of the
  // query if one was present, otherwise the current
  // time at the backend.
  //
  // Available since protocol **v10**.
  otp_timestamp: Integer;

  // Current OTP confirmation string of the device.
  // Matches exactly the string that would be returned
  // as part of a payment confirmation for the given
  // amount and time (so may contain multiple OTP codes).
  //
  // If the otp_algorithm is time-based, the code is
  // returned for the current time, or for the faketime
  // if a TIMESTAMP query argument was provided by the client.
  //
  // When using OTP with counters, the counter is **NOT**
  // increased merely because this endpoint created
  // an OTP code (this is a GET request, after all!).
  //
  // If the otp_algorithm requires an amount, the
  // amount argument must be specified in the
  // query, otherwise the otp_code is not
  // generated.
  //
  // This field is *optional* in the response, as it is
  // only provided if we could compute it based on the
  // otp_algorithm and matching client query arguments.
  //
  // Available since protocol **v10**.
  otp_code?: string;
}
export interface TemplateAddDetails {
  // Template ID to use.
  template_id: string;

  // Human-readable description for the template.
  template_description: string;

  // OTP device ID.
  // This parameter is optional.
  otp_id?: string;

  // Additional information in a separate template.
  template_contract: TemplateContractDetails;

  // Key-value pairs matching a subset of the
  // fields from template_contract that are
  // user-editable defaults for this template.
  // Since protocol **v13**.
  editable_defaults?: TemplateContractDetailsDefaults;
}
export interface TemplateContractDetails {
  // Human-readable summary for the template.
  summary?: string;

  // Required currency for payments to the template.
  // The user may specify any amount, but it must be
  // in this currency.
  // This parameter is optional and should not be present
  // if "amount" is given.
  currency?: string;

  // The price is imposed by the merchant and cannot be changed by the customer.
  // This parameter is optional.
  amount?: AmountString;

  // Minimum age buyer must have (in years). Default is 0.
  minimum_age: Integer;

  // The time the customer need to pay before his order will be deleted.
  // It is deleted if the customer did not pay and if the duration is over.
  pay_duration: RelativeTime;
}

export interface TemplateContractDetailsDefaults {
  summary?: string;

  currency?: string;

  /**
   * Amount *or* a plain currency string.
   */
  amount?: string;
}

export interface TemplatePatchDetails {
  // Human-readable description for the template.
  template_description: string;

  // OTP device ID.
  // This parameter is optional.
  otp_id?: string;

  // Additional information in a separate template.
  template_contract: TemplateContractDetails;

  // Key-value pairs matching a subset of the
  // fields from template_contract that are
  // user-editable defaults for this template.
  // Since protocol **v13**.
  editable_defaults?: TemplateContractDetailsDefaults;
}

export interface TemplateSummaryResponse {
  // List of templates that are present in our backend.
  templates: TemplateEntry[];
}

export interface TemplateEntry {
  // Template identifier, as found in the template.
  template_id: string;

  // Human-readable description for the template.
  template_description: string;
}

export interface WalletTemplateDetails {
  // Hard-coded information about the contrac terms
  // for this template.
  template_contract: TemplateContractDetails;

  // Key-value pairs matching a subset of the
  // fields from template_contract that are
  // user-editable defaults for this template.
  // Since protocol **v13**.
  editable_defaults?: TemplateContractDetailsDefaults;
}

export interface TemplateDetails {
  // Human-readable description for the template.
  template_description: string;

  // OTP device ID.
  // This parameter is optional.
  otp_id?: string;

  // Additional information in a separate template.
  template_contract: TemplateContractDetails;

  // Key-value pairs matching a subset of the
  // fields from template_contract that are
  // user-editable defaults for this template.
  // Since protocol **v13**.
  editable_defaults?: TemplateContractDetailsDefaults;
}
export interface UsingTemplateDetails {
  // Summary of the template
  summary?: string;

  // The amount entered by the customer.
  amount?: AmountString;
}

export interface WebhookAddDetails {
  // Webhook ID to use.
  webhook_id: string;

  // The event of the webhook: why the webhook is used.
  event_type: string;

  // URL of the webhook where the customer will be redirected.
  url: string;

  // Method used by the webhook
  http_method: string;

  // Header template of the webhook
  header_template?: string;

  // Body template by the webhook
  body_template?: string;
}

export interface WebhookPatchDetails {
  // The event of the webhook: why the webhook is used.
  event_type: string;

  // URL of the webhook where the customer will be redirected.
  url: string;

  // Method used by the webhook
  http_method: string;

  // Header template of the webhook
  header_template?: string;

  // Body template by the webhook
  body_template?: string;
}

export interface WebhookSummaryResponse {
  // Return webhooks that are present in our backend.
  webhooks: WebhookEntry[];
}

export interface WebhookEntry {
  // Webhook identifier, as found in the webhook.
  webhook_id: string;

  // The event of the webhook: why the webhook is used.
  event_type: string;
}

export interface WebhookDetails {
  // The event of the webhook: why the webhook is used.
  event_type: string;

  // URL of the webhook where the customer will be redirected.
  url: string;

  // Method used by the webhook
  http_method: string;

  // Header template of the webhook
  header_template?: string;

  // Body template by the webhook
  body_template?: string;
}

export interface TokenFamilyCreateRequest {
  // Identifier for the token family consisting of unreserved characters
  // according to RFC 3986.
  slug: string;

  // Human-readable name for the token family.
  name: string;

  // Human-readable description for the token family.
  description: string;

  // Optional map from IETF BCP 47 language tags to localized descriptions.
  description_i18n?: { [lang_tag: string]: string };

  // Start time of the token family's validity period.
  // If not specified, merchant backend will use the current time.
  valid_after?: Timestamp;

  // End time of the token family's validity period.
  valid_before: Timestamp;

  // Validity duration of an issued token.
  duration: RelativeTime;

  // Rounding granularity for the start validity of keys.
  // The desired time is rounded down to a multiple of this
  // granularity and then the start_offset is added to
  // compute the actual start time of the token keys' validity.
  // The end is then computed by adding the duration.
  // Must be 1 minute, 1 hour, 1 day, 1 week, 30 days, 90 days
  // or 365 days (1 year).
  validity_granularity: RelativeTime;

  // Offset to add to the start time rounded to validity_granularity
  // to compute the actual start time for a key.
  // Default is zero.
  start_offset?: Integer;

  // Kind of the token family.
  kind: TokenFamilyKind;
}

export enum TokenFamilyKind {
  Discount = "discount",
  Subscription = "subscription",
}

export interface TokenFamilyUpdateRequest {
  // Human-readable name for the token family.
  name: string;

  // Human-readable description for the token family.
  description: string;

  // Optional map from IETF BCP 47 language tags to localized descriptions.
  description_i18n: { [lang_tag: string]: string };

  // Start time of the token family's validity period.
  valid_after: Timestamp;

  // End time of the token family's validity period.
  valid_before: Timestamp;

  // Validity duration of an issued token.
  duration: RelativeTime;
}

export interface TokenFamiliesList {
  // All configured token families of this instance.
  token_families: TokenFamilySummary[];
}

export interface TokenFamilySummary {
  // Identifier for the token family consisting of unreserved characters
  // according to RFC 3986.
  slug: string;

  // Human-readable name for the token family.
  name: string;

  // Start time of the token family's validity period.
  valid_after: Timestamp;

  // End time of the token family's validity period.
  valid_before: Timestamp;

  // Kind of the token family.
  kind: TokenFamilyKind;
}

export interface TokenFamilyDetails {
  // Identifier for the token family consisting of unreserved characters
  // according to RFC 3986.
  slug: string;

  // Human-readable name for the token family.
  name: string;

  // Human-readable description for the token family.
  description: string;

  // Optional map from IETF BCP 47 language tags to localized descriptions.
  description_i18n?: { [lang_tag: string]: string };

  // Start time of the token family's validity period.
  valid_after: Timestamp;

  // End time of the token family's validity period.
  valid_before: Timestamp;

  // Validity duration of an issued token.
  duration: RelativeTime;

  // Kind of the token family.
  kind: TokenFamilyKind;

  // How many tokens have been issued for this family.
  issued: Integer;

  // How many tokens have been used for this family.
  used: Integer;
}

export interface OrderChoice {
  // Total price for the choice. The exchange will subtract deposit
  // fees from that amount before transferring it to the merchant.
  amount: AmountString;

  // Human readable description of the semantics of the choice
  // within the contract to be shown to the user at payment.
  description?: string;

  // Map from IETF 47 language tags to localized descriptions.
  description_i18n?: InternationalizedString;

  // Inputs that must be provided by the customer, if this choice is selected.
  // Defaults to empty array if not specified.
  inputs?: OrderInput[];

  // Outputs provided by the merchant, if this choice is selected.
  // Defaults to empty array if not specified.
  outputs?: OrderOutput[];

  // Maximum total deposit fee accepted by the merchant for this contract.
  // Overrides defaults of the merchant instance.
  max_fee?: AmountString;
}

export enum OrderInputType {
  Token = "token",
}

export type OrderInput = OrderInputToken;

export interface OrderInputToken {
  // Token input.
  type: OrderInputType.Token;

  // Token family slug as configured in the merchant backend. Slug is unique
  // across all configured tokens of a merchant.
  token_family_slug: string;

  // How many units of the input are required.
  // Defaults to 1 if not specified. Output with count == 0 are ignored by
  // the merchant backend.
  count?: Integer;
}

export enum OrderOutputType {
  Token = "token",
  TaxReceipt = "tax-receipt",
}

export type OrderOutput = OrderOutputToken | OrderOutputTaxReceipt;

export interface OrderOutputToken {
  type: OrderOutputType.Token;

  // Token family slug as configured in the merchant backend. Slug is unique
  // across all configured tokens of a merchant.
  token_family_slug: string;

  // How many units of the output are issued by the merchant.
  // Defaults to 1 if not specified. Output with count == 0 are ignored by
  // the merchant backend.
  count?: Integer;

  // When should the output token be valid. Can be specified if the
  // desired validity period should be in the future (like selling
  // a subscription for the next month). Optional. If not given,
  // the validity is supposed to be "now" (time of order creation).
  valid_at?: TalerPreciseTimestamp;
}

export interface OrderOutputTaxReceipt {
  type: OrderOutputType.TaxReceipt;

  // Total amount that will be on the tax receipt.
  // Optional, if missing the full amount will be on the receipt.
  amount?: AmountString;

  donau_urls: string[];
}

export interface OrderCommon {
  // Human-readable description of the whole purchase.
  summary: string;

  // Map from IETF BCP 47 language tags to localized summaries.
  summary_i18n?: { [lang_tag: string]: string };

  // Unique, free-form identifier for the proposal.
  // Must be unique within a merchant instance.
  // For merchants that do not store proposals in their DB
  // before the customer paid for them, the order_id can be used
  // by the frontend to restore a proposal from the information
  // encoded in it (such as a short product identifier and timestamp).
  order_id?: string;

  // URL where the same contract could be ordered again (if
  // available). Returned also at the public order endpoint
  // for people other than the actual buyer (hence public,
  // in case order IDs are guessable).
  public_reorder_url?: string;

  // URL that will show that the order was successful after
  // it has been paid for.  Optional. When POSTing to the
  // merchant, the placeholder "${ORDER_ID}" will be
  // replaced with the actual order ID (useful if the
  // order ID is generated server-side and needs to be
  // in the URL).
  // Note that this placeholder can only be used once.
  // Either fulfillment_url or fulfillment_message must be specified.
  fulfillment_url?: string;

  // Message shown to the customer after paying for the order.
  // Either fulfillment_url or fulfillment_message must be specified.
  fulfillment_message?: string;

  // Map from IETF BCP 47 language tags to localized fulfillment
  // messages.
  fulfillment_message_i18n?: { [lang_tag: string]: string };

  // List of products that are part of the purchase (see Product).
  products?: Product[];

  // Time when this contract was generated.
  timestamp?: Timestamp;

  // After this deadline has passed, no refunds will be accepted.
  refund_deadline?: Timestamp;

  // After this deadline, the merchant won't accept payments for the contract.
  pay_deadline?: Timestamp;

  // Transfer deadline for the exchange.  Must be in the
  // deposit permissions of coins used to pay for this order.
  wire_transfer_deadline?: Timestamp;

  // Base URL of the (public!) merchant backend API.
  // Must be an absolute URL that ends with a slash.
  merchant_base_url?: string;

  // Wire transfer method identifier for the wire method associated with h_wire.
  // The wallet may only select exchanges via a matching auditor if the
  // exchange also supports this wire method.
  // The wire transfer fees must be added based on this wire transfer method.
  wire_method?: string;

  // Delivery location for (all!) products.
  delivery_location?: Location;

  // Time indicating when the order should be delivered.
  // May be overwritten by individual products.
  delivery_date?: Timestamp;

  // Specifies for how long the wallet should try to get an
  // automatic refund for the purchase. If this field is
  // present, the wallet should wait for a few seconds after
  // the purchase and then automatically attempt to obtain
  // a refund.  The wallet should probe until "delay"
  // after the payment was successful (i.e. via long polling
  // or via explicit requests with exponential back-off).
  //
  // In particular, if the wallet is offline
  // at that time, it MUST repeat the request until it gets
  // one response from the merchant after the delay has expired.
  // If the refund is granted, the wallet MUST automatically
  // recover the payment.  This is used in case a merchant
  // knows that it might be unable to satisfy the contract and
  // desires for the wallet to attempt to get the refund without any
  // customer interaction.  Note that it is NOT an error if the
  // merchant does not grant a refund.
  auto_refund?: RelativeTime;

  // Extra data that is only interpreted by the merchant frontend.
  // Useful when the merchant needs to store extra information on a
  // contract without storing it separately in their database.
  extra?: any;

  // Minimum age buyer must have (in years). Default is 0.
  minimum_age?: Integer;
}

export enum OrderVersion {
  V0 = 0,
  V1 = 1,
}

export interface OrderV0 extends OrderCommon {
  // Optional, defaults to 0 if not set.
  version?: OrderVersion.V0;

  // Total price for the transaction. The exchange will subtract deposit
  // fees from that amount before transferring it to the merchant.
  amount: AmountString;

  // Maximum total deposit fee accepted by the merchant for this contract.
  // Overrides defaults of the merchant instance.
  max_fee?: AmountString;
}

export interface OrderV1 extends OrderCommon {
  // Version 1 order support discounts and subscriptions.
  // https://docs.taler.net/design-documents/046-mumimo-contracts.html
  // @since protocol **vSUBSCRIBE**
  version: OrderVersion.V1;

  // List of contract choices that the customer can select from.
  // @since protocol **vSUBSCRIBE**
  choices?: OrderChoice[];
}

export interface Product {
  // Merchant-internal identifier for the product.
  product_id?: string;

  // Name of the product.
  // Since API version **v20**.  Optional only for
  // backwards-compatibility, should be considered mandatory
  // moving forward!
  product_name?: string;

  // Human-readable product description.
  description: string;

  // Map from IETF BCP 47 language tags to localized descriptions.
  description_i18n?: { [lang_tag: string]: string };

  // The number of units of the product to deliver to the customer.
  quantity?: Integer;

  // Unit in which the product is measured (liters, kilograms, packages, etc.).
  unit?: string;

  // The price of the product; this is the total price for quantity times unit of this product.
  price?: AmountString;

  // An optional base64-encoded product image.
  image?: ImageDataUrl;

  // A list of taxes paid by the merchant for this product. Can be empty.
  taxes?: Tax[];

  // Time indicating when this product should be delivered.
  delivery_date?: Timestamp;
}

export interface Tax {
  // The name of the tax.
  name: string;

  // Amount paid in tax.
  tax: AmountString;
}

export interface Merchant {
  // The merchant's legal name of business.
  name: string;

  // Label for a location with the business address of the merchant.
  email?: string;

  // Label for a location with the business address of the merchant.
  website?: string;

  // An optional base64-encoded product image.
  logo?: ImageDataUrl;

  // Label for a location with the business address of the merchant.
  address?: Location;

  // Label for a location that denotes the jurisdiction for disputes.
  // Some of the typical fields for a location (such as a street address) may be absent.
  jurisdiction?: Location;
}

// Delivery location, loosely modeled as a subset of
// ISO20022's PostalAddress25.
export interface Location {
  // Nation with its own government.
  country?: string;

  // Identifies a subdivision of a country such as state, region, county.
  country_subdivision?: string;

  // Identifies a subdivision within a country sub-division.
  district?: string;

  // Name of a built-up area, with defined boundaries, and a local government.
  town?: string;

  // Specific location name within the town.
  town_location?: string;

  // Identifier consisting of a group of letters and/or numbers that
  // is added to a postal address to assist the sorting of mail.
  post_code?: string;

  // Name of a street or thoroughfare.
  street?: string;

  // Name of the building or house.
  building_name?: string;

  // Number that identifies the position of a building on a street.
  building_number?: string;

  // Free-form address lines, should not exceed 7 elements.
  address_lines?: string[];
}

export interface Exchange {
  // The exchange's base URL.
  url: string;

  // How much would the merchant like to use this exchange.
  // The wallet should use a suitable exchange with high
  // priority. The following priority values are used, but
  // it should be noted that they are NOT in any way normative.
  //
  // 0: likely it will not work (recently seen with account
  //    restriction that would be bad for this merchant)
  // 512: merchant does not know, might be down (merchant
  //    did not yet get /wire response).
  // 1024: good choice (recently confirmed working)
  priority: Integer;

  // Master public key of the exchange.
  master_pub: EddsaPublicKey;

  // Maximum amount that the merchant could be paid
  // using this exchange (due to legal limits).
  // New in protocol **v17**.
  // Optional, no limit if missing.
  max_contribution?: AmountString;
}

export interface MerchantReserveCreateConfirmation {
  // Public key identifying the reserve.
  reserve_pub: EddsaPublicKey;

  // Wire accounts of the exchange where to transfer the funds.
  accounts: ExchangeWireAccount[];
}

export interface TemplateEditableDetails {
  // Human-readable summary for the template.
  summary?: string;

  // Required currency for payments to the template.
  // The user may specify any amount, but it must be
  // in this currency.
  // This parameter is optional and should not be present
  // if "amount" is given.
  currency?: string;

  // The price is imposed by the merchant and cannot be changed by the customer.
  // This parameter is optional.
  amount?: AmountString;
}

export interface MerchantTemplateContractDetails {
  // Human-readable summary for the template.
  summary?: string;

  // The price is imposed by the merchant and cannot be changed by the customer.
  // This parameter is optional.
  amount?: string;

  // Minimum age buyer must have (in years). Default is 0.
  minimum_age: number;

  // The time the customer need to pay before his order will be deleted.
  // It is deleted if the customer did not pay and if the duration is over.
  pay_duration: TalerProtocolDuration;
}

export interface MerchantTemplateAddDetails {
  // Template ID to use.
  template_id: string;

  // Human-readable description for the template.
  template_description: string;

  // A base64-encoded image selected by the merchant.
  // This parameter is optional.
  // We are not sure about it.
  image?: string;

  editable_defaults?: TemplateEditableDetails;

  // Additional information in a separate template.
  template_contract: MerchantTemplateContractDetails;

  // OTP device ID.
  // This parameter is optional.
  otp_id?: string;
}

const codecForExchangeConfigInfo = (): Codec<ExchangeConfigInfo> =>
  buildCodecForObject<ExchangeConfigInfo>()
    .property("base_url", codecForString())
    .property("currency", codecForString())
    .property("master_pub", codecForEddsaPublicKey())
    .build("TalerMerchantApi.ExchangeConfigInfo");

//     // Set when the merchant supports
// // self-provisioning instances.
// // Since protocol **v21**
// have_self_provisioning: boolean;

// // True if this merchant backend supports the Donau
// // extension and can thus issue donation receipts.
// // Should primarily be used to control the SPA's CRUD
// // functionality for Donau.
// // @since **v21**
// have_donau: boolean;

// // Tan channels that are required
// // to be confirmed for an instance to
// // be useable.
// // @since **v21**
// mandatory_tan_channels?: TanChannel[];

// // Space-separated list of enabled payment target types.
// // Useful if the SPA should not show allow adding other
// // types of bank accounts. "*" is used to represent no
// // restriction.
// // @since **v22**
// payment_target_types: string;

// // Regular expression representing further restrictions
// // on allowed payment targets.  Any "payto://"-URI supplied
// // for a bank account must match the given regular expression.
// // For example, "payto://iban/CH.*" would restrict the system
// // to only Swiss bank accounts.
// // Optional, no restrictions are imposed if the field is
// // absent.
// // @since **v22**
// payment_target_regex? string;

// // Default wire transfer delay for new instances.
// // This is the default to use for new instances, see the instance value for
// // the instance-specific default.
// // @since **v22**
// default_wire_transfer_delay: RelativeTime;

// // Default payment delay for new instances.
// // This is the default to use for new instances, see the instance value for
// // the instance-specific default.
// // @since **v22**
// default_pay_delay: RelativeTime;

// // If the frontend does NOT specify a refund deadline, how long should
// // refunds be allowed by default?
// // This is the default to use for new instances, see the instance value for
// // the instance-specific default.
// // @since **v22**
// default_refund_delay: RelativeTime;

export const codecForTalerMerchantConfigResponse =
  (): Codec<MerchantVersionResponse> =>
    buildCodecForObject<MerchantVersionResponse>()
      .property("name", codecForConstString("taler-merchant"))
      .property("currency", codecForString())
      .property("version", codecForString())
      .property("currencies", codecForMap(codecForCurrencySpecificiation()))
      .property("exchanges", codecForList(codecForExchangeConfigInfo()))
      .property("implementation", codecOptional(codecForString()))
      .property(
        "have_self_provisioning",
        codecOptionalDefault(codecForBoolean(), false),
      )
      .property("have_donau", codecOptionalDefault(codecForBoolean(), false))
      .property(
        "mandatory_tan_channels",
        codecOptionalDefault(
          codecForList(
            codecForEither(
              codecForConstString(TanChannel.SMS),
              codecForConstString(TanChannel.EMAIL),
            ),
          ),
          [],
        ),
      )
      .property("default_pay_delay", codecOptional(codecForDuration))
      .property("default_refund_delay", codecOptional(codecForDuration))
      .property("default_wire_transfer_delay", codecOptional(codecForDuration))
      .property(
        "payment_target_regex",
        codecOptionalDefault(codecForString(), "*"),
      )
      .property(
        "payment_target_types",
        codecOptionalDefault(codecForString(), "*"),
      )
      .build("TalerMerchantApi.VersionResponse");

export const codecForClaimResponse = (): Codec<ClaimResponse> =>
  buildCodecForObject<ClaimResponse>()
    // Must be 'any', otherwise, contract terms won't match.
    .property("contract_terms", codecForAny())
    .property("sig", codecForEddsaSignature())
    .build("TalerMerchantApi.ClaimResponse");

export const codecForPaymentResponse = (): Codec<PaymentResponse> =>
  buildCodecForObject<PaymentResponse>()
    .property("pos_confirmation", codecOptional(codecForString()))
    .property("sig", codecForEddsaSignature())
    .build("TalerMerchantApi.PaymentResponse");

export const codecForPaymentDeniedLegallyResponse =
  (): Codec<PaymentDeniedLegallyResponse> =>
    buildCodecForObject<PaymentDeniedLegallyResponse>()
      .property("exchange_base_urls", codecForList(codecForString()))
      .build("TalerMerchantApi.PaymentDeniedLegallyResponse");

export const codecForStatusPaid = (): Codec<StatusPaid> =>
  buildCodecForObject<StatusPaid>()
    .property("refund_amount", codecForAmountString())
    .property("refund_pending", codecForBoolean())
    .property("refund_taken", codecForAmountString())
    .property("refunded", codecForBoolean())
    .property("type", codecForConstString("paid"))
    .build("TalerMerchantApi.StatusPaid");

export const codecForStatusGoto = (): Codec<StatusGotoResponse> =>
  buildCodecForObject<StatusGotoResponse>()
    .property("public_reorder_url", codecForURLString())
    .property("type", codecForConstString("goto"))
    .build("TalerMerchantApi.StatusGotoResponse");

export const codecForStatusStatusUnpaid = (): Codec<StatusUnpaidResponse> =>
  buildCodecForObject<StatusUnpaidResponse>()
    .property("type", codecForConstString("unpaid"))
    .property("already_paid_order_id", codecOptional(codecForString()))
    .property("fulfillment_url", codecOptional(codecForString()))
    .property("taler_pay_uri", codecForTalerUriString())
    .build("TalerMerchantApi.PaymentResponse");

export const codecForPaidRefundStatusResponse =
  (): Codec<PaidRefundStatusResponse> =>
    buildCodecForObject<PaidRefundStatusResponse>()
      .property("pos_confirmation", codecOptional(codecForString()))
      .property("refunded", codecForBoolean())
      .build("TalerMerchantApi.PaidRefundStatusResponse");

export const codecForMerchantAbortPayRefundSuccessStatus =
  (): Codec<MerchantAbortPayRefundSuccessStatus> =>
    buildCodecForObject<MerchantAbortPayRefundSuccessStatus>()
      .property("exchange_pub", codecForString())
      .property("exchange_sig", codecForString())
      .property("exchange_status", codecForConstNumber(200))
      .property("type", codecForConstString("success"))
      .build("TalerMerchantApi.MerchantAbortPayRefundSuccessStatus");

export const codecForMerchantAbortPayRefundFailureStatus =
  (): Codec<MerchantAbortPayRefundFailureStatus> =>
    buildCodecForObject<MerchantAbortPayRefundFailureStatus>()
      .property("exchange_code", codecForNumber())
      .property("exchange_reply", codecForAny())
      .property("exchange_status", codecForNumber())
      .property("type", codecForConstString("failure"))
      .build("TalerMerchantApi.MerchantAbortPayRefundFailureStatus");

export const codecForMerchantAbortPayRefundUndepositedStatus =
  (): Codec<MerchantAbortPayRefundUndepositedStatus> =>
    buildCodecForObject<MerchantAbortPayRefundUndepositedStatus>()
      .property("type", codecForConstString("undeposited"))
      .build("TalerMerchantApi.MerchantAbortPayRefundUndepositedStatus");

export const codecForMerchantAbortPayRefundStatus =
  (): Codec<MerchantAbortPayRefundStatus> =>
    buildCodecForUnion<MerchantAbortPayRefundStatus>()
      .discriminateOn("type")
      .alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
      .alternative("failure", codecForMerchantAbortPayRefundFailureStatus())
      .alternative(
        "undeposited",
        codecForMerchantAbortPayRefundUndepositedStatus(),
      )
      .build("TalerMerchantApi.MerchantAbortPayRefundStatus");

export const codecForAbortResponse = (): Codec<AbortResponse> =>
  buildCodecForObject<AbortResponse>()
    .property("refunds", codecForList(codecForMerchantAbortPayRefundStatus()))
    .build("TalerMerchantApi.AbortResponse");

export const codecForWalletRefundResponse = (): Codec<WalletRefundResponse> =>
  buildCodecForObject<WalletRefundResponse>()
    .property("merchant_pub", codecForEddsaPublicKey())
    .property("refund_amount", codecForAmountString())
    .property("refunds", codecForList(codecForMerchantCoinRefundStatus()))
    .build("TalerMerchantApi.AbortResponse");

export const codecForMerchantCoinRefundSuccessStatus =
  (): Codec<MerchantCoinRefundSuccessStatus> =>
    buildCodecForObject<MerchantCoinRefundSuccessStatus>()
      .property("type", codecForConstString("success"))
      .property("coin_pub", codecForEddsaPublicKey())
      .property("exchange_status", codecForConstNumber(200))
      .property("exchange_sig", codecForEddsaSignature())
      .property("rtransaction_id", codecForNumber())
      .property("refund_amount", codecForAmountString())
      .property("exchange_pub", codecForEddsaPublicKey())
      .property("execution_time", codecForTimestamp)
      .build("TalerMerchantApi.MerchantCoinRefundSuccessStatus");

export const codecForMerchantCoinRefundFailureStatus =
  (): Codec<MerchantCoinRefundFailureStatus> =>
    buildCodecForObject<MerchantCoinRefundFailureStatus>()
      .property("type", codecForConstString("failure"))
      .property("coin_pub", codecForEddsaPublicKey())
      .property("exchange_status", codecForNumber())
      .property("rtransaction_id", codecForNumber())
      .property("refund_amount", codecForAmountString())
      .property("exchange_code", codecOptional(codecForNumber()))
      .property("exchange_reply", codecOptional(codecForAny()))
      .property("execution_time", codecForTimestamp)
      .build("TalerMerchantApi.MerchantCoinRefundFailureStatus");

export const codecForMerchantCoinRefundStatus =
  (): Codec<MerchantCoinRefundStatus> =>
    buildCodecForUnion<MerchantCoinRefundStatus>()
      .discriminateOn("type")
      .alternative("success", codecForMerchantCoinRefundSuccessStatus())
      .alternative("failure", codecForMerchantCoinRefundFailureStatus())
      .build("TalerMerchantApi.MerchantCoinRefundStatus");

export const codecForMerchantAuthMethod = codecForEither(
  codecForConstString(MerchantAuthMethod.TOKEN),
);

export const codecForQueryInstancesResponse =
  (): Codec<QueryInstancesResponse> =>
    buildCodecForObject<QueryInstancesResponse>()
      .property("name", codecForString())
      .property("email", codecOptional(codecForString()))
      .property("phone_number", codecOptional(codecForString()))
      .property("website", codecOptional(codecForString()))
      .property("email_validated", codecOptional(codecForBoolean()))
      .property("phone_validated", codecOptional(codecForBoolean()))
      .property("logo", codecOptional(codecForString()))
      .property("merchant_pub", codecForEddsaPublicKey())
      .property("address", codecForLocation())
      .property("jurisdiction", codecForLocation())
      .property("use_stefan", codecForBoolean())
      .property("default_wire_transfer_delay", codecForDuration)
      .property("default_pay_delay", codecForDuration)
      .property(
        "auth",
        buildCodecForObject<{
          method: MerchantAuthMethod.TOKEN;
        }>()
          .property("method", codecForMerchantAuthMethod)
          .build("TalerMerchantApi.QueryInstancesResponse.auth"),
      )
      .build("TalerMerchantApi.QueryInstancesResponse");

export const codecForAccountKycRedirects =
  (): Codec<MerchantAccountKycRedirectsResponse> =>
    buildCodecForObject<MerchantAccountKycRedirectsResponse>()
      .property("kyc_data", codecForList(codecForMerchantAccountKycRedirect()))

      .build("TalerMerchantApi.MerchantAccountKycRedirectsResponse");

export const codecForMerchantAccountKycStatus = codecForEither(
  codecForConstString(MerchantAccountKycStatus.AWAITING_AML_REVIEW),
  codecForConstString(MerchantAccountKycStatus.EXCHANGE_GATEWAY_TIMEOUT),
  codecForConstString(MerchantAccountKycStatus.EXCHANGE_INTERNAL_ERROR),
  codecForConstString(MerchantAccountKycStatus.EXCHANGE_STATUS_INVALID),
  codecForConstString(MerchantAccountKycStatus.EXCHANGE_UNREACHABLE),
  codecForConstString(MerchantAccountKycStatus.KYC_REQUIRED),
  codecForConstString(MerchantAccountKycStatus.KYC_WIRE_IMPOSSIBLE),
  codecForConstString(MerchantAccountKycStatus.KYC_WIRE_REQUIRED),
  codecForConstString(MerchantAccountKycStatus.LOGIC_BUG),
  codecForConstString(MerchantAccountKycStatus.NO_EXCHANGE_KEY),
  codecForConstString(MerchantAccountKycStatus.READY),
);

export const codecForMerchantAccountKycRedirect =
  (): Codec<MerchantAccountKycRedirect> =>
    buildCodecForObject<MerchantAccountKycRedirect>()
      .property("status", codecForMerchantAccountKycStatus)
      .property("h_wire", codecForString())
      .property("payto_uri", codecForPaytoString())
      .property("exchange_url", codecForURLString())
      .property("exchange_http_status", codecForNumber())
      .property("no_keys", codecForBoolean())
      .property("auth_conflict", codecForBoolean())
      .property("exchange_code", codecOptional(codecForNumber()))
      .property("access_token", codecOptional(codecForAccessToken()))
      .property("limits", codecOptional(codecForList(codecForAccountLimit())))
      .property("payto_kycauths", codecOptional(codecForList(codecForString())))
      .build("TalerMerchantApi.MerchantAccountKycRedirect");

export const codecForTokenScope = codecForEither(
  codecForConstString(LoginTokenScope.All),
  codecForConstString(LoginTokenScope.Spa),
  codecForConstString(LoginTokenScope.OrderFull),
  codecForConstString(LoginTokenScope.OrderManagement),
  codecForConstString(LoginTokenScope.OrderPos),
  codecForConstString(LoginTokenScope.OrderSimple),
  codecForConstString(LoginTokenScope.ReadOnly),
  codecForConstString(LoginTokenScope.All_Refreshable),
  codecForConstString(LoginTokenScope.Spa_Refreshable),
  codecForConstString(LoginTokenScope.OrderFull_Refreshable),
  codecForConstString(LoginTokenScope.OrderManagement_Refreshable),
  codecForConstString(LoginTokenScope.OrderPos_Refreshable),
  codecForConstString(LoginTokenScope.OrderSimple_Refreshable),
  codecForConstString(LoginTokenScope.ReadOnly_Refreshable),
);

export const codecForLoginTokenSuccessResponse =
  (): Codec<LoginTokenSuccessResponse> =>
    buildCodecForObject<LoginTokenSuccessResponse>()
      .property("scope", codecForTokenScope)
      .property("access_token", codecForAccessToken())
      .property("expiration", codecForTimestamp)
      .property("refreshable", codecForBoolean())
      .build("TalerMerchantApi.LoginTokenSuccessResponse");

export const codecForExchangeKycTimeout = (): Codec<ExchangeKycTimeout> =>
  buildCodecForObject<ExchangeKycTimeout>()
    .property("exchange_url", codecForURLString())
    .property("exchange_code", codecForNumber())
    .property("exchange_http_status", codecForNumber())
    .build("TalerMerchantApi.ExchangeKycTimeout");

export const codecForAccountAddResponse = (): Codec<AccountAddResponse> =>
  buildCodecForObject<AccountAddResponse>()
    .property("h_wire", codecForString())
    .property("salt", codecForString())
    .build("TalerMerchantApi.AccountAddResponse");

export const codecForAccountsSummaryResponse =
  (): Codec<AccountsSummaryResponse> =>
    buildCodecForObject<AccountsSummaryResponse>()
      .property("accounts", codecForList(codecForBankAccountEntry()))
      .build("TalerMerchantApi.AccountsSummaryResponse");

export const codecForBankAccountEntry = (): Codec<BankAccountEntry> =>
  buildCodecForObject<BankAccountEntry>()
    .property("payto_uri", codecForPaytoString())
    .property("h_wire", codecForString())
    .property("active", codecOptional(codecForBoolean()))
    .build("TalerMerchantApi.BankAccountEntry");

export const codecForBankAccountDetail = (): Codec<BankAccountDetail> =>
  buildCodecForObject<BankAccountDetail>()
    .property("payto_uri", codecForPaytoString())
    .property("h_wire", codecForString())
    .property("salt", codecForString())
    .property("credit_facade_url", codecOptional(codecForURLString()))
    .property("active", codecOptional(codecForBoolean()))
    .build("TalerMerchantApi.BankAccountEntry");

export const codecForCategoryListResponse = (): Codec<CategoryListResponse> =>
  buildCodecForObject<CategoryListResponse>()
    .property("categories", codecForList(codecForCategoryListEntry()))
    .build("TalerMerchantApi.CategoryListResponse");

export const codecForCategoryListEntry = (): Codec<CategoryListEntry> =>
  buildCodecForObject<CategoryListEntry>()
    .property("category_id", codecForNumber())
    .property("name", codecForString())
    .property("name_i18n", codecForInternationalizedString())
    .property("product_count", codecForNumber())
    .build("TalerMerchantApi.CategoryListEntry");

export const codecForCategoryProductList = (): Codec<CategoryProductList> =>
  buildCodecForObject<CategoryProductList>()
    .property("name", codecForString())
    .property("name_i18n", codecForInternationalizedString())
    .property("products", codecForList(codecForProductSummary()))
    .build("TalerMerchantApi.CategoryProductList");

export const codecForProductSummary = (): Codec<ProductSummary> =>
  buildCodecForObject<ProductSummary>()
    .property("product_id", codecForString())
    // .property("description", codecForString())
    // .property("description_i18n", codecForInternationalizedString())
    .build("TalerMerchantApi.ProductSummary");

export const codecForInventorySummaryResponse =
  (): Codec<InventorySummaryResponse> =>
    buildCodecForObject<InventorySummaryResponse>()
      .property("products", codecForList(codecForInventoryEntry()))
      .build("TalerMerchantApi.InventorySummaryResponse");

export const codecForInventoryEntry = (): Codec<InventoryEntry> =>
  buildCodecForObject<InventoryEntry>()
    .property("product_id", codecForString())
    .property("product_serial", codecForNumber())
    .build("TalerMerchantApi.InventoryEntry");

export const codecForMerchantPosProductDetail =
  (): Codec<MerchantPosProductDetail> =>
    buildCodecForObject<MerchantPosProductDetail>()
      .property("product_serial", codecForNumber())
      .property("product_id", codecOptional(codecForString()))
      .property("product_name", codecOptional(codecForString()))
      .property("categories", codecForList(codecForNumber()))
      .property("description", codecForString())
      .property("description_i18n", codecForInternationalizedString())
      .property("unit", codecForString())
      .property("price", codecForAmountString())
      .property("image", codecForString())
      .property("taxes", codecOptional(codecForList(codecForTax())))
      .property("total_stock", codecForNumber())
      .property("minimum_age", codecOptional(codecForNumber()))
      .build("TalerMerchantApi.MerchantPosProductDetail");

export const codecForMerchantCategory = (): Codec<MerchantCategory> =>
  buildCodecForObject<MerchantCategory>()
    .property("id", codecForNumber())
    .property("name", codecForString())
    .property("name_i18n", codecForInternationalizedString())
    .build("TalerMerchantApi.MerchantCategory");

export const codecForFullInventoryDetailsResponse =
  (): Codec<FullInventoryDetailsResponse> =>
    buildCodecForObject<FullInventoryDetailsResponse>()
      .property("categories", codecForList(codecForMerchantCategory()))
      .property("products", codecForList(codecForMerchantPosProductDetail()))
      .build("TalerMerchantApi.FullInventoryDetailsResponse");

export const codecForProductDetail = (): Codec<ProductDetail> =>
  buildCodecForObject<ProductDetail>()
    .property("description", codecForString())
    .property("description_i18n", codecForInternationalizedString())
    .property("unit", codecForString())
    .property("product_name", codecOptional(codecForString()))
    .property("price", codecForAmountString())
    .property("image", codecForString())
    .property("categories", codecForList(codecForNumber()))
    .property("taxes", codecOptional(codecForList(codecForTax())))
    .property("address", codecOptional(codecForLocation()))
    .property("next_restock", codecOptional(codecForTimestamp))
    .property("total_stock", codecForNumber())
    .property("total_sold", codecForNumber())
    .property("total_lost", codecForNumber())
    .property("minimum_age", codecOptional(codecForNumber()))
    .build("TalerMerchantApi.ProductDetail");

export const codecForTax = (): Codec<Tax> =>
  buildCodecForObject<Tax>()
    .property("name", codecForString())
    .property("tax", codecForAmountString())
    .build("TalerMerchantApi.Tax");

export const codecForPostOrderResponse = (): Codec<PostOrderResponse> =>
  buildCodecForObject<PostOrderResponse>()
    .property("order_id", codecForString())
    .property("pay_deadline", codecOptional(codecForTimestamp))
    .property("token", codecOptional(codecForString()))
    .build("TalerMerchantApi.PostOrderResponse");

export const codecForOutOfStockResponse = (): Codec<OutOfStockResponse> =>
  buildCodecForObject<OutOfStockResponse>()
    .property("product_id", codecForString())
    .property("available_quantity", codecForNumber())
    .property("requested_quantity", codecForNumber())
    .property("restock_expected", codecForTimestamp)
    .build("TalerMerchantApi.OutOfStockResponse");

export const codecForOrderHistory = (): Codec<OrderHistory> =>
  buildCodecForObject<OrderHistory>()
    .property("orders", codecForList(codecForOrderHistoryEntry()))
    .build("TalerMerchantApi.OrderHistory");

export const codecForOrderHistoryEntry = (): Codec<OrderHistoryEntry> =>
  buildCodecForObject<OrderHistoryEntry>()
    .property("order_id", codecForString())
    .property("row_id", codecForNumber())
    .property("timestamp", codecForTimestamp)
    .property("amount", codecForAmountString())
    .property("summary", codecForString())
    .property("refundable", codecForBoolean())
    .property("paid", codecForBoolean())
    .build("TalerMerchantApi.OrderHistoryEntry");

export const codecForMerchant = (): Codec<Merchant> =>
  buildCodecForObject<Merchant>()
    .property("name", codecForString())
    .property("email", codecOptional(codecForString()))
    .property("logo", codecOptional(codecForString()))
    .property("website", codecOptional(codecForString()))
    .property("address", codecOptional(codecForLocation()))
    .property("jurisdiction", codecOptional(codecForLocation()))
    .build("TalerMerchantApi.MerchantInfo");

export const codecForExchange = (): Codec<Exchange> =>
  buildCodecForObject<Exchange>()
    .property("master_pub", codecForEddsaPublicKey())
    .property("priority", codecForNumber())
    .property("url", codecForString())
    .property("max_contribution", codecOptional(codecForAmountString()))
    .build("TalerMerchantApi.Exchange");

const codecForOrderCommon = (): ObjectCodec<OrderCommon> =>
  buildCodecForObject<Order>()
    .property("order_id", codecOptional(codecForString()))
    .property("public_reorder_url", codecOptional(codecForString()))
    .property("fulfillment_url", codecOptional(codecForString()))
    .property("fulfillment_message", codecOptional(codecForString()))
    .property(
      "fulfillment_message_i18n",
      codecOptional(codecForInternationalizedString()),
    )
    .property("merchant_base_url", codecOptional(codecForString()))
    .property("auto_refund", codecOptional(codecForDuration))
    .property("summary", codecForString())
    .property("summary_i18n", codecOptional(codecForInternationalizedString()))
    .property("pay_deadline", codecOptional(codecForTimestamp))
    .property("refund_deadline", codecOptional(codecForTimestamp))
    .property("wire_transfer_deadline", codecOptional(codecForTimestamp))
    .property("timestamp", codecOptional(codecForTimestamp))
    .property("delivery_location", codecOptional(codecForLocation()))
    .property("delivery_date", codecOptional(codecForTimestamp))
    .property("products", codecOptional(codecForList(codecForProduct())))
    .property("extra", codecOptional(codecForAny()))
    .build("TalerMerchantApi.Order");

export const codecForOrderV0 = (): Codec<OrderV0> =>
  buildCodecForObject<OrderV0>()
    .property("version", codecOptional(codecForConstNumber(OrderVersion.V0)))
    .property("amount", codecForAmountString())
    .property("max_fee", codecOptional(codecForAmountString()))
    .mixin(codecForOrderCommon())
    .build("TalerMerchantApi.OrderV0");

export const codecForOrderV1 = (): Codec<OrderV1> =>
  buildCodecForObject<OrderV1>()
    .property("version", codecForConstNumber(OrderVersion.V1))
    .property("choices", codecForList(codecForOrderChoice()))
    .mixin(codecForOrderCommon())
    .build("TalerMerchantApi.OrderV1");

export const codecForOrder = (): Codec<Order> =>
  buildCodecForUnion<Order>()
    .discriminateOn("version")
    .alternative(undefined, codecForOrderV0())
    .alternative(OrderVersion.V0, codecForOrderV0())
    .alternative(OrderVersion.V1, codecForOrderV1())
    .build("TalerMerchantApi.Order");

export const codecForOrderChoice = (): Codec<OrderChoice> =>
  buildCodecForObject<OrderChoice>()
    .property("amount", codecForAmountString())
    .property("description", codecOptional(codecForString()))
    .property(
      "description_i18n",
      codecOptional(codecForInternationalizedString()),
    )
    .property("max_fee", codecOptional(codecForAmountString()))
    .property("inputs", codecOptional(codecForList(codecForOrderInput())))
    .property("outputs", codecOptional(codecForList(codecForOrderOutput())))
    .build("TalerMerchantApi.OrderChoice");

export const codecForOrderInput = (): Codec<OrderInput> =>
  buildCodecForUnion<OrderInput>()
    .discriminateOn("type")
    .alternative(OrderInputType.Token, codecForOrderInputToken())
    .build("TalerMerchantApi.OrderInput");

export const codecForOrderInputToken = (): Codec<OrderInputToken> =>
  buildCodecForObject<OrderInputToken>()
    .property("type", codecForConstString(OrderInputType.Token))
    .property("token_family_slug", codecForString())
    .property("count", codecOptional(codecForNumber()))
    .build("TalerMerchantApi.OrderInputToken");

export const codecForOrderOutput = (): Codec<OrderOutput> =>
  buildCodecForUnion<OrderOutput>()
    .discriminateOn("type")
    .alternative(OrderOutputType.Token, codecForOrderOutputToken())
    .alternative(OrderOutputType.TaxReceipt, codecForOrderOutputTaxReceipt())
    .build("TalerMerchantApi.OrderOutput");

export const codecForOrderOutputToken = (): Codec<OrderOutputToken> =>
  buildCodecForObject<OrderOutputToken>()
    .property("type", codecForConstString(OrderOutputType.Token))
    .property("token_family_slug", codecForString())
    .property("count", codecOptional(codecForNumber()))
    .property("valid_at", codecOptional(codecForPreciseTimestamp))
    .build("TalerMerchantApi.OrderOutputToken");

export const codecForOrderOutputTaxReceipt = (): Codec<OrderOutputTaxReceipt> =>
  buildCodecForObject<OrderOutputTaxReceipt>()
    .property("type", codecForConstString(OrderOutputType.TaxReceipt))
    .property("amount", codecOptional(codecForAmountString()))
    .property("donau_urls", codecForList(codecForStringURL()))
    .build("TalerMerchantApi.OrderOutputTaxReceipt");

export const codecForProduct = (): Codec<Product> =>
  buildCodecForObject<Product>()
    .property("product_id", codecOptional(codecForString()))
    .property("product_name", codecOptional(codecForString()))
    .property("description", codecForString())
    .property(
      "description_i18n",
      codecOptional(codecForInternationalizedString()),
    )
    .property("quantity", codecOptional(codecForNumber()))
    .property("unit", codecOptional(codecForString()))
    .property("price", codecOptional(codecForAmountString()))
    .property("image", codecOptional(codecForString()))
    .property("taxes", codecOptional(codecForList(codecForTax())))
    .property("delivery_date", codecOptional(codecForTimestamp))
    .build("TalerMerchantApi.Product");

export const codecForCheckPaymentPaidResponse =
  (): Codec<CheckPaymentPaidResponse> =>
    buildCodecForObject<CheckPaymentPaidResponse>()
      .property("order_status", codecForConstString("paid"))
      .property("refunded", codecForBoolean())
      .property("refund_pending", codecForBoolean())
      .property("wired", codecForBoolean())
      .property("deposit_total", codecForAmountString())
      .property("exchange_code", codecForNumber())
      .property("exchange_http_status", codecForNumber())
      .property("refund_amount", codecForAmountString())
      .property("contract_terms", codecForMerchantContractTerms())
      .property("wire_reports", codecForList(codecForTransactionWireReport()))
      .property("wire_details", codecForList(codecForTransactionWireTransfer()))
      .property("refund_details", codecForList(codecForRefundDetails()))
      .property("order_status_url", codecForURLString())
      .build("TalerMerchantApi.CheckPaymentPaidResponse");

export const codecForCheckPaymentUnpaidResponse =
  (): Codec<CheckPaymentUnpaidResponse> =>
    buildCodecForObject<CheckPaymentUnpaidResponse>()
      .property("order_status", codecForConstString("unpaid"))
      .property("taler_pay_uri", codecForTalerUriString())
      .property("creation_time", codecForTimestamp)
      .property("pay_deadline", codecOptional(codecForTimestamp))
      .property("summary", codecForString())
      .property("total_amount", codecForAmountString())
      .property("already_paid_order_id", codecOptional(codecForString()))
      .property("already_paid_fulfillment_url", codecOptional(codecForString()))
      .property("order_status_url", codecForString())
      .build("TalerMerchantApi.CheckPaymentUnpaidResponse");

export const codecForCheckPaymentClaimedResponse =
  (): Codec<CheckPaymentClaimedResponse> =>
    buildCodecForObject<CheckPaymentClaimedResponse>()
      .property("order_status", codecForConstString("claimed"))
      .property("contract_terms", codecForMerchantContractTerms())
      .property("order_status_url", codecForString())
      .build("TalerMerchantApi.CheckPaymentClaimedResponse");

export const codecForMerchantOrderPrivateStatusResponse =
  (): Codec<MerchantOrderStatusResponse> =>
    buildCodecForUnion<MerchantOrderStatusResponse>()
      .discriminateOn("order_status")
      .alternative("paid", codecForCheckPaymentPaidResponse())
      .alternative("unpaid", codecForCheckPaymentUnpaidResponse())
      .alternative("claimed", codecForCheckPaymentClaimedResponse())
      .build("TalerMerchantApi.MerchantOrderStatusResponse");

export const codecForRefundDetails = (): Codec<RefundDetails> =>
  buildCodecForObject<RefundDetails>()
    .property("reason", codecForString())
    .property("pending", codecForBoolean())
    .property("timestamp", codecForTimestamp)
    .property("amount", codecForAmountString())
    .build("TalerMerchantApi.RefundDetails");

export const codecForTransactionWireTransfer =
  (): Codec<TransactionWireTransfer> =>
    buildCodecForObject<TransactionWireTransfer>()
      .property("exchange_url", codecForURLString())
      .property("wtid", codecForString())
      .property("execution_time", codecForTimestamp)
      .property("amount", codecForAmountString())
      .property("confirmed", codecForBoolean())
      .build("TalerMerchantApi.TransactionWireTransfer");

export const codecForTransactionWireReport = (): Codec<TransactionWireReport> =>
  buildCodecForObject<TransactionWireReport>()
    .property("code", codecForNumber())
    .property("hint", codecForString())
    .property("exchange_code", codecForNumber())
    .property("exchange_http_status", codecForNumber())
    .property("coin_pub", codecForEddsaPublicKey())
    .build("TalerMerchantApi.TransactionWireReport");

export const codecForMerchantRefundResponse =
  (): Codec<MerchantRefundResponse> =>
    buildCodecForObject<MerchantRefundResponse>()
      .property("taler_refund_uri", codecForTalerUriString())
      .property("h_contract", codecForString())
      .build("TalerMerchantApi.MerchantRefundResponse");

export const codecForTansferList = (): Codec<TransferList> =>
  buildCodecForObject<TransferList>()
    .property("transfers", codecForList(codecForTransferDetails()))
    .build("TalerMerchantApi.TransferList");

export const codecForTransferDetails = (): Codec<TransferDetails> =>
  buildCodecForObject<TransferDetails>()
    .property("credit_amount", codecForAmountString())
    .property("wtid", codecForString())
    .property("payto_uri", codecForPaytoString())
    .property("exchange_url", codecForURLString())
    .property("transfer_serial_id", codecForNumber())
    .property("execution_time", codecOptional(codecForTimestamp))
    .property("verified", codecOptional(codecForBoolean()))
    .property("confirmed", codecOptional(codecForBoolean()))
    .build("TalerMerchantApi.TransferDetails");

export const codecForOtpDeviceSummaryResponse =
  (): Codec<OtpDeviceSummaryResponse> =>
    buildCodecForObject<OtpDeviceSummaryResponse>()
      .property("otp_devices", codecForList(codecForOtpDeviceEntry()))
      .build("TalerMerchantApi.OtpDeviceSummaryResponse");

export const codecForOtpDeviceEntry = (): Codec<OtpDeviceEntry> =>
  buildCodecForObject<OtpDeviceEntry>()
    .property("otp_device_id", codecForString())
    .property("device_description", codecForString())
    .build("TalerMerchantApi.OtpDeviceEntry");

export const codecForOtpDeviceDetails = (): Codec<OtpDeviceDetails> =>
  buildCodecForObject<OtpDeviceDetails>()
    .property("device_description", codecForString())
    .property("otp_algorithm", codecForNumber())
    .property("otp_ctr", codecOptional(codecForNumber()))
    .property("otp_timestamp", codecForNumber())
    .property("otp_code", codecOptional(codecForString()))
    .build("TalerMerchantApi.OtpDeviceDetails");

export const codecForTemplateSummaryResponse =
  (): Codec<TemplateSummaryResponse> =>
    buildCodecForObject<TemplateSummaryResponse>()
      .property("templates", codecForList(codecForTemplateEntry()))
      .build("TalerMerchantApi.TemplateSummaryResponse");

export const codecForTemplateEntry = (): Codec<TemplateEntry> =>
  buildCodecForObject<TemplateEntry>()
    .property("template_id", codecForString())
    .property("template_description", codecForString())
    .build("TalerMerchantApi.TemplateEntry");

export const codecForTemplateDetails = (): Codec<TemplateDetails> =>
  buildCodecForObject<TemplateDetails>()
    .property("template_description", codecForString())
    .property("otp_id", codecOptional(codecForString()))
    .property("template_contract", codecForTemplateContractDetails())
    .property(
      "editable_defaults",
      codecOptional(codecForTemplateContractDetailsDefaults()),
    )
    .build("TalerMerchantApi.TemplateDetails");

export const codecForTemplateContractDetails =
  (): Codec<TemplateContractDetails> =>
    buildCodecForObject<TemplateContractDetails>()
      .property("summary", codecOptional(codecForString()))
      .property("currency", codecOptional(codecForString()))
      .property("amount", codecOptional(codecForAmountString()))
      .property("minimum_age", codecForNumber())
      .property("pay_duration", codecForDuration)
      .build("TalerMerchantApi.TemplateContractDetails");

export const codecForTemplateContractDetailsDefaults =
  (): Codec<TemplateContractDetailsDefaults> =>
    buildCodecForObject<TemplateContractDetailsDefaults>()
      .property("summary", codecOptional(codecForString()))
      .property("currency", codecOptional(codecForString()))
      .property("amount", codecOptional(codecForAmountString()))
      .build("TalerMerchantApi.TemplateContractDetailsDefaults");

export const codecForWalletTemplateDetails = (): Codec<WalletTemplateDetails> =>
  buildCodecForObject<WalletTemplateDetails>()
    .property("template_contract", codecForTemplateContractDetails())
    .property(
      "editable_defaults",
      codecOptional(codecForTemplateContractDetailsDefaults()),
    )
    .build("TalerMerchantApi.WalletTemplateDetails");

export const codecForWebhookSummaryResponse =
  (): Codec<WebhookSummaryResponse> =>
    buildCodecForObject<WebhookSummaryResponse>()
      .property("webhooks", codecForList(codecForWebhookEntry()))
      .build("TalerMerchantApi.WebhookSummaryResponse");

export const codecForWebhookEntry = (): Codec<WebhookEntry> =>
  buildCodecForObject<WebhookEntry>()
    .property("webhook_id", codecForString())
    .property("event_type", codecForString())
    .build("TalerMerchantApi.WebhookEntry");

export const codecForWebhookDetails = (): Codec<WebhookDetails> =>
  buildCodecForObject<WebhookDetails>()
    .property("event_type", codecForString())
    .property("url", codecForString())
    .property("http_method", codecForString())
    .property("header_template", codecOptional(codecForString()))
    .property("body_template", codecOptional(codecForString()))
    .build("TalerMerchantApi.WebhookDetails");

export const codecForTokenFamilyKind = codecForEither(
  codecForConstString(TokenFamilyKind.Discount),
  codecForConstString(TokenFamilyKind.Subscription),
);

export const codecForTokenFamilyDetails = (): Codec<TokenFamilyDetails> =>
  buildCodecForObject<TokenFamilyDetails>()
    .property("slug", codecForString())
    .property("name", codecForString())
    .property("description", codecForString())
    .property(
      "description_i18n",
      codecOptional(codecForInternationalizedString()),
    )
    .property("valid_after", codecForTimestamp)
    .property("valid_before", codecForTimestamp)
    .property("duration", codecForDuration)
    .property("kind", codecForTokenFamilyKind)
    .property("issued", codecForNumber())
    .property("used", codecForNumber())
    .build("TalerMerchantApi.TokenFamilyDetails");

export const codecForTokenFamiliesList = (): Codec<TokenFamiliesList> =>
  buildCodecForObject<TokenFamiliesList>()
    .property("token_families", codecForList(codecForTokenFamilySummary()))
    .build("TalerMerchantApi.TokenFamiliesList");

export const codecForTokenFamilySummary = (): Codec<TokenFamilySummary> =>
  buildCodecForObject<TokenFamilySummary>()
    .property("slug", codecForString())
    .property("name", codecForString())
    .property("valid_after", codecForTimestamp)
    .property("valid_before", codecForTimestamp)
    .property("kind", codecForTokenFamilyKind)
    .build("TalerMerchantApi.TokenFamilySummary");

export const codecForInstancesResponse = (): Codec<InstancesResponse> =>
  buildCodecForObject<InstancesResponse>()
    .property("instances", codecForList(codecForInstance()))
    .build("TalerMerchantApi.InstancesResponse");

export const codecForInstance = (): Codec<Instance> =>
  buildCodecForObject<Instance>()
    .property("name", codecForString())
    .property("website", codecOptional(codecForString()))
    .property("logo", codecOptional(codecForString()))
    .property("id", codecForString())
    .property("merchant_pub", codecForEddsaPublicKey())
    .property("payment_targets", codecForList(codecForString()))
    .property("deleted", codecForBoolean())
    .build("TalerMerchantApi.Instance");

export const codecForTemplateEditableDetails =
  (): Codec<TemplateEditableDetails> =>
    buildCodecForObject<TemplateEditableDetails>()
      .property("summary", codecOptional(codecForString()))
      .property("currency", codecOptional(codecForString()))
      .property("amount", codecOptional(codecForAmountString()))
      .build("TemplateEditableDetails");

export const codecForMerchantReserveCreateConfirmation =
  (): Codec<MerchantReserveCreateConfirmation> =>
    buildCodecForObject<MerchantReserveCreateConfirmation>()
      .property("accounts", codecForList(codecForExchangeWireAccount()))
      .property("reserve_pub", codecForEddsaPublicKey())
      .build("MerchantReserveCreateConfirmation");

export interface ChallengeResponse {
  // List of challenge IDs that must be solved before the
  // client may proceed.
  challenges: Challenge[];

  // True if **all** challenges must be solved (AND), false if
  // it is sufficient to solve one of them (OR).
  combi_and: boolean;
}
export interface Challenge {
  // Unique identifier of the challenge to solve to run this protected
  // operation.
  challenge_id: string;

  // Channel of the last successful transmission of the TAN challenge.
  tan_channel: TanChannel;

  // Info of the last successful transmission of the TAN challenge.
  // Hint to show to the user as to where the challenge was
  // sent or what to use to solve the challenge. May not
  // contain the full address for privacy.
  tan_info: string;
}

export enum TanChannel {
  SMS = "sms",
  EMAIL = "email",
}

export const codecForChallenge = (): Codec<Challenge> =>
  buildCodecForObject<Challenge>()
    .property("challenge_id", codecForString())
    .property(
      "tan_channel",
      codecForEither(
        codecForConstString(TanChannel.SMS),
        codecForConstString(TanChannel.EMAIL),
      ),
    )
    .property("tan_info", codecForString())
    .build("MFA.Challenge");

export const codecForChallengeResponse = (): Codec<ChallengeResponse> =>
  buildCodecForObject<ChallengeResponse>()
    .property("challenges", codecForList(codecForChallenge()))
    .property("combi_and", codecForBoolean())
    .build("MFA.ChallengeResponse");

export interface ChallengeRequestResponse {
  // FIXME: this response fields are mandatory but
  // put it optional from the client side to
  // handle server that doesn't support this response.
  // Remove it when all server has been upgraded

  /**
   * How long does the client have to solve the challenge.
   */
  solve_expiration?: Timestamp;

  /**
   * What is the earlist time at which the client may request a new challenge to be transmitted?
   */
  earliest_retransmission?: Timestamp;
}

export const codecForChallengeRequestResponse =
  (): Codec<ChallengeRequestResponse> =>
    buildCodecForObject<ChallengeRequestResponse>()
      .property("solve_expiration", codecOptional(codecForTimestamp))
      .property("earliest_retransmission", codecOptional(codecForTimestamp))
      .build("MFA.ChallengeRequestResponse");

export interface ChallengeSolveRequest {
  // The TAN code that solves $CHALLENGE_ID.
  tan: string;
}

export interface MerchantPostDonauBody {
  donau_url: string;
  charity_id: number;
}
