FastTax C# Rest Code Snippet

using System.Web;

namespace fast_tax_dot_net.REST
{
    /// <summary>
    /// Provides functionality to call the ServiceObjects FastTax (FT) REST API's GetBestMatch endpoint,
    /// retrieving tax rate information (e.g., total tax rate, city, county, state rates) for a given US address
    /// with fallback to a backup endpoint for reliability in live mode.
    /// </summary>
    public static class GetBestMatchClient
    {
        // Base URL constants: production, backup, and trial
        private const string LiveBaseUrl = "https://sws.serviceobjects.com/ft/web.svc/json/";
        private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/ft/web.svc/json/";
        private const string TrialBaseUrl = "https://trial.serviceobjects.com/ft/web.svc/json/";

        /// <summary>
        /// Synchronously calls the GetBestMatch REST endpoint to retrieve tax rate information,
        /// attempting the primary endpoint first and falling back to the backup if the response is invalid
        /// (Error.Number == "4") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including address, city, state, zip, tax type, and license key.</param>
        /// <returns>Deserialized <see cref="GetBestMatchResponse"/> containing tax rate data or an error.</returns>
        public static GetBestMatchResponse Invoke(GetBestMatchInput input)
        {
            // Use query string parameters so missing/optional fields don't break the URL
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            GetBestMatchResponse response = Helper.HttpGet<GetBestMatchResponse>(url, input.TimeoutSeconds);

            // Fallback on error in live mode
            if (input.IsLive && !IsValid(response))
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                GetBestMatchResponse fallbackResponse = Helper.HttpGet<GetBestMatchResponse>(fallbackUrl, input.TimeoutSeconds);
                return fallbackResponse;
            }

            return response;
        }

        /// <summary>
        /// Asynchronously calls the GetBestMatch REST endpoint to retrieve tax rate information,
        /// attempting the primary endpoint first and falling back to the backup if the response is invalid
        /// (Error.Number == "4") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including address, city, state, zip, tax type, and license key.</param>
        /// <returns>Deserialized <see cref="GetBestMatchResponse"/> containing tax rate data or an error.</returns>
        public static async Task<GetBestMatchResponse> InvokeAsync(GetBestMatchInput input)
        {
            // Use query string parameters so missing/optional fields don't break the URL
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            GetBestMatchResponse response = await Helper.HttpGetAsync<GetBestMatchResponse>(url, input.TimeoutSeconds).ConfigureAwait(false);

            // Fallback on error in live mode
            if (input.IsLive && !IsValid(response))
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                GetBestMatchResponse fallbackResponse = await Helper.HttpGetAsync<GetBestMatchResponse>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
                return fallbackResponse;
            }

            return response;
        }

        // Build the full request URL, including URL-encoded query string
        public static string BuildUrl(GetBestMatchInput input, string baseUrl)
        {
            // Construct query string with URL-encoded parameters
            string qs = $"GetBestMatch?" +
                        $"Address={HttpUtility.UrlEncode(input.Address)}" +
                        $"&Address2={HttpUtility.UrlEncode(input.Address2)}" +
                        $"&City={HttpUtility.UrlEncode(input.City)}" +
                        $"&State={HttpUtility.UrlEncode(input.State)}" +
                        $"&Zip={HttpUtility.UrlEncode(input.Zip)}" +
                        $"&TaxType={HttpUtility.UrlEncode(input.TaxType)}" +
                        $"&LicenseKey={HttpUtility.UrlEncode(input.LicenseKey)}";
            return baseUrl + qs;
        }

        private static bool IsValid(GetBestMatchResponse response) => response?.Error == null || response.Error.Number != "4";

        /// <summary>
        /// Input parameters for the GetBestMatch API call. Represents a US address to retrieve tax rates
        /// with cascading logic for partial matches.
        /// </summary>
        /// <param name="Address">Address line of the address to get tax rates for (e.g., "123 Main Street").</param>
        /// <param name="Address2">Secondary address line (e.g., "Apt 4B"). Optional.</param>
        /// <param name="City">The city of the address (e.g., "New York"). Optional if zip is provided.</param>
        /// <param name="State">The state of the address (e.g., "NY"). Optional if zip is provided.</param>
        /// <param name="Zip">The ZIP code of the address. Optional if city and state are provided.</param>
        /// <param name="TaxType">The type of tax to look for ("sales" or "use").</param>
        /// <param name="LicenseKey">The license key to authenticate the API request.</param>
        /// <param name="IsLive">Indicates whether to use the live service (true) or trial service (false).</param>
        /// <param name="TimeoutSeconds">Timeout duration for the API call, in seconds.</param>
        public record GetBestMatchInput(
            string Address = "",
            string Address2 = "",
            string City = "",
            string State = "",
            string Zip = "",
            string TaxType = "",
            string LicenseKey = "",
            bool IsLive = true,
            int TimeoutSeconds = 15
        );
    }
}


using System.Runtime.Serialization;
using System.Linq;

namespace fast_tax_dot_net.REST
{
    /// <summary>
    /// Response from GetBestMatch API, containing tax rate information for the best match.
    /// </summary>
    [DataContract]
    public class GetBestMatchResponse
    {
        public BestMatchTaxInfo[] TaxInfoItems { get; set; }
        public string MatchLevel { get; set; }
        public Error Error { get; set; }
        public string[] Debug { get; set; }

        public override string ToString()
        {
            string taxInfoItemsStr = TaxInfoItems != null
                ? string.Join("\n", TaxInfoItems.Select(taxInfo => taxInfo.ToString()))
                : "null";
            string debugStr = Debug != null
                ? string.Join(", ", Debug)
                : "null";
            return $"GetBestMatchResponse:\n" +
                   $"TaxInfoItems:\n{taxInfoItemsStr}\n" +
                   $"MatchLevel: {MatchLevel}\n" +
                   $"Error: {(Error != null ? Error.ToString() : "null")}\n" +
                   $"Debug: [{debugStr}]";
        }
    }

    /// <summary>
    /// Tax information for a matched address or ZIP code.
    /// </summary>
    [DataContract]
    public class BestMatchTaxInfo
    {
        public string Zip { get; set; }
        public string City { get; set; }
        public string County { get; set; }
        public string StateAbbreviation { get; set; }
        public string StateName { get; set; }
        public string TaxRate { get; set; }
        public string StateRate { get; set; }
        public string CityRate { get; set; }
        public string CountyRate { get; set; }
        public string CountyDistrictRate { get; set; }
        public string CityDistrictRate { get; set; }
        public string SpecialDistrictRate { get; set; }
        public InformationComponent[] InformationComponents { get; set; }
        public string TotalTaxExempt { get; set; }
        public string NotesCodes { get; set; }
        public string NotesDesc { get; set; }
        public override string ToString()
        {
            string infoComponentsStr = InformationComponents != null
                ? string.Join(", ", InformationComponents.Select(ic => ic.ToString()))
                : "null";
            return $"BestMatchTaxInfo:\n" +
                   $"Zip: {Zip}\n" +
                   $"City: {City}\n" +
                   $"County: {County}\n" +
                   $"StateAbbreviation: {StateAbbreviation}\n" +
                   $"StateName: {StateName}\n" +
                   $"TaxRate: {TaxRate}\n" +
                   $"StateRate: {StateRate}\n" +
                   $"CityRate: {CityRate}\n" +
                   $"CountyRate: {CountyRate}\n" +
                   $"CountyDistrictRate: {CountyDistrictRate}\n" +
                   $"CityDistrictRate: {CityDistrictRate}\n" +
                   $"SpecialDistrictRate: {SpecialDistrictRate}\n" +
                   $"InformationComponents: [{infoComponentsStr}]\n" +
                   $"TotalTaxExempt: {TotalTaxExempt}\n" +
                   $"NotesCodes: {NotesCodes}\n" +
                   $"NotesDesc: {NotesDesc}";
        }
    }

    /// <summary>
    /// Information component containing name-value pairs for additional tax information.
    /// </summary>
    [DataContract]
    public class InformationComponent
    {
        public string Name { get; set; }
        public string Value { get; set; }
        public override string ToString()
        {
            return $"Name: {Name}, Value: {Value}";
        }
    }

    /// <summary>
    /// Error object for API responses.
    /// </summary>
    [DataContract]
    public class Error
    {
        public string Desc { get; set; }
        public string Number { get; set; }
        public string Location { get; set; }
        public override string ToString()
        {
            return $"Desc: {Desc}, Number: {Number}, Location: {Location}";
        }
    }
}


using System.Text.Json;
using System.Web;

namespace fast_tax_dot_net.REST
{
    public class Helper
    {
        public static T HttpGet<T>(string url, int timeoutSeconds)
        {
            using var httpClient = new HttpClient
            {
                Timeout = TimeSpan.FromSeconds(timeoutSeconds)
            };
            using var request = new HttpRequestMessage(HttpMethod.Get, url);
            using HttpResponseMessage response = httpClient
                .SendAsync(request)
                .GetAwaiter()
                .GetResult();
            response.EnsureSuccessStatusCode();
            using Stream responseStream = response.Content
                .ReadAsStreamAsync()
                .GetAwaiter()
                .GetResult();
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
            object? obj = JsonSerializer.Deserialize(responseStream, typeof(T), options);
            T result = (T)obj!;
            return result;
        }

        // Asynchronous HTTP GET and JSON deserialize
        public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds)
        {
            HttpClient HttpClient = new HttpClient();
            HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
            using var httpResponse = await HttpClient.GetAsync(url).ConfigureAwait(false);
            httpResponse.EnsureSuccessStatusCode();
            var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
            return JsonSerializer.Deserialize<T>(stream)!;
        }

        public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty);
    }
}

FastTax Python Rest Code Snippet

from ft_response import GetBestMatchResponse, BestMatchTaxInfo, InformationComponent, Error
import requests

# Endpoint URLs for ServiceObjects FastTax (FT) API
primary_url = "https://sws.serviceobjects.com/ft/web.svc/json/GetBestMatch?"
backup_url = "https://swsbackup.serviceobjects.com/ft/web.svc/json/GetBestMatch?"
trial_url = "https://trial.serviceobjects.com/ft/web.svc/json/GetBestMatch?"

def get_best_match(
    address: str,
    address2: str,
    city: str,
    state: str,
    zip: str,
    tax_type: str,
    license_key: str,
    is_live: bool = True
) -> GetBestMatchResponse:
    """
    Call ServiceObjects FastTax (FT) API's GetBestMatch endpoint
    to retrieve tax rate information (e.g., total tax rate, city, county, state rates) for a given US address.

    Parameters:
        address: Address line of the address to get tax rates for (e.g., "123 Main Street").
        address2: Secondary address line (e.g., "Apt 4B"). Optional.
        city: The city of the address (e.g., "New York"). Optional if zip is provided.
        state: The state of the address (e.g., "NY"). Optional if zip is provided.
        zip: The ZIP code of the address. Optional if city and state are provided.
        tax_type: The type of tax to look for ("sales" or "use").
        license_key: Your ServiceObjects license key.
        is_live: Use live or trial servers.

    Returns:
        GetBestMatchResponse: Parsed JSON response with tax rate results or error details.

    Raises:
        RuntimeError: If the API returns an error payload.
        requests.RequestException: On network/HTTP failures (trial mode).
    """
    params = {
        "Address": address,
        "Address2": address2,
        "City": city,
        "State": state,
        "Zip": zip,
        "TaxType": tax_type,
        "LicenseKey": license_key,
    }
    # Select the base URL: production vs trial
    url = primary_url if is_live else trial_url

    try:
        # Attempt primary (or trial) endpoint
        response = requests.get(url, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()

        # If API returned an error in JSON payload, trigger fallback
        error = data.get('Error')
        if not (error is None or error.get('Number') != "4"):
            if is_live:
                # Try backup URL
                response = requests.get(backup_url, params=params, timeout=10)
                response.raise_for_status()
                data = response.json()

                # If still error, propagate exception
                if 'Error' in data:
                    raise RuntimeError(f"FastTax service error: {data['Error']}")
            else:
                # Trial mode error is terminal
                raise RuntimeError(f"FastTax trial error: {data['Error']}")

        # Convert JSON response to GetBestMatchResponse for structured access
        error = Error(**data.get("Error", {})) if data.get("Error") else None

        return GetBestMatchResponse(
            TaxInfoItems=[
                BestMatchTaxInfo(
                    Zip=ti.get("Zip"),
                    City=ti.get("City"),
                    County=ti.get("County"),
                    StateAbbreviation=ti.get("StateAbbreviation"),
                    StateName=ti.get("StateName"),
                    TaxRate=ti.get("TaxRate"),
                    StateRate=ti.get("StateRate"),
                    CityRate=ti.get("CityRate"),
                    CountyRate=ti.get("CountyRate"),
                    CountyDistrictRate=ti.get("CountyDistrictRate"),
                    CityDistrictRate=ti.get("CityDistrictRate"),
                    SpecialDistrictRate=ti.get("SpecialDistrictRate"),
                    InformationComponents=[
                        InformationComponent(Name=comp.get("Name"), Value=comp.get("Value"))
                        for comp in ti.get("InformationComponents", [])
                    ] if "InformationComponents" in ti else [],
                    TotalTaxExempt=ti.get("TotalTaxExempt"),
                    NotesCodes=ti.get("NotesCodes"),
                    NotesDesc=ti.get("NotesDesc")
                )
                for ti in data.get("TaxInfoItems", [])
            ] if "TaxInfoItems" in data else [],
            MatchLevel=data.get("MatchLevel"),
            Error=error,
            Debug=data.get("Debug", [])
        )

    except requests.RequestException as req_exc:
        # Network or HTTP-level error occurred
        if is_live:
            try:
                # Fallback to backup URL
                response = requests.get(backup_url, params=params, timeout=10)
                response.raise_for_status()
                data = response.json()
                if "Error" in data:
                    raise RuntimeError(f"FastTax backup error: {data['Error']}") from req_exc

                error = Error(**data.get("Error", {})) if data.get("Error") else None

                return GetBestMatchResponse(
                    TaxInfoItems=[
                        BestMatchTaxInfo(
                            Zip=ti.get("Zip"),
                            City=ti.get("City"),
                            County=ti.get("County"),
                            StateAbbreviation=ti.get("StateAbbreviation"),
                            StateName=ti.get("StateName"),
                            TaxRate=ti.get("TaxRate"),
                            StateRate=ti.get("StateRate"),
                            CityRate=ti.get("CityRate"),
                            CountyRate=ti.get("CountyRate"),
                            CountyDistrictRate=ti.get("CountyDistrictRate"),
                            CityDistrictRate=ti.get("CityDistrictRate"),
                            SpecialDistrictRate=ti.get("SpecialDistrictRate"),
                            InformationComponents=[
                                InformationComponent(Name=comp.get("Name"), Value=comp.get("Value"))
                                for comp in ti.get("InformationComponents", [])
                            ] if "InformationComponents" in ti else [],
                            TotalTaxExempt=ti.get("TotalTaxExempt"),
                            NotesCodes=ti.get("NotesCodes"),
                            NotesDesc=ti.get("NotesDesc")
                        )
                        for ti in data.get("TaxInfoItems", [])
                    ] if "TaxInfoItems" in data else [],
                    MatchLevel=data.get("MatchLevel"),
                    Error=error,
                    Debug=data.get("Debug", [])
                )
            except Exception as backup_exc:
                raise RuntimeError("FastTax service unreachable on both endpoints") from backup_exc
        else:
            raise RuntimeError(f"FastTax trial error: {str(req_exc)}") from req_exc


from dataclasses import dataclass
from typing import Optional, List


@dataclass
class GetBestMatchInput:
    Address: Optional[str] = None
    Address2: Optional[str] = None
    City: Optional[str] = None
    State: Optional[str] = None
    Zip: Optional[str] = None
    TaxType: Optional[str] = None
    LicenseKey: Optional[str] = None
    IsLive: bool = True
    TimeoutSeconds: int = 15

    def __str__(self) -> str:
        return (f"GetBestMatchInput: Address={self.Address}, Address2={self.Address2}, City={self.City}, "
                f"State={self.State}, Zip={self.Zip}, TaxType={self.TaxType}, LicenseKey={self.LicenseKey}, "
                f"IsLive={self.IsLive}, TimeoutSeconds={self.TimeoutSeconds}")


@dataclass
class InformationComponent:
    Name: Optional[str] = None
    Value: Optional[str] = None

    def __str__(self) -> str:
        return f"InformationComponent: Name={self.Name}, Value={self.Value}"


@dataclass
class Error:
    Desc: Optional[str] = None
    Number: Optional[str] = None
    Location: Optional[str] = None

    def __str__(self) -> str:
        return f"Error: Desc={self.Desc}, Number={self.Number}, Location={self.Location}"


@dataclass
class BestMatchTaxInfo:
    Zip: Optional[str] = None
    City: Optional[str] = None
    County: Optional[str] = None
    StateAbbreviation: Optional[str] = None
    StateName: Optional[str] = None
    TaxRate: Optional[float] = None
    StateRate: Optional[float] = None
    CityRate: Optional[float] = None
    CountyRate: Optional[float] = None
    CountyDistrictRate: Optional[float] = None
    CityDistrictRate: Optional[float] = None
    SpecialDistrictRate: Optional[float] = None
    InformationComponents: Optional[List['InformationComponent']] = None
    TotalTaxExempt: Optional[str] = None
    NotesCodes: Optional[str] = None
    NotesDesc: Optional[str] = None

    def __post_init__(self):
        if self.InformationComponents is None:
            self.InformationComponents = []

    def __str__(self) -> str:
        components_string = ', '.join(str(component) for component in self.InformationComponents) if self.InformationComponents else 'None'
        return (f"BestMatchTaxInfo: Zip={self.Zip}, City={self.City}, County={self.County}, "
                f"StateAbbreviation={self.StateAbbreviation}, StateName={self.StateName}, "
                f"TaxRate={self.TaxRate}, StateRate={self.StateRate}, CityRate={self.CityRate}, "
                f"CountyRate={self.CountyRate}, CountyDistrictRate={self.CountyDistrictRate}, "
                f"CityDistrictRate={self.CityDistrictRate}, SpecialDistrictRate={self.SpecialDistrictRate}, "
                f"InformationComponents=[{components_string}], TotalTaxExempt={self.TotalTaxExempt}, "
                f"NotesCodes={self.NotesCodes}, NotesDesc={self.NotesDesc}")


@dataclass
class GetBestMatchResponse:
    TaxInfoItems: Optional[List['BestMatchTaxInfo']] = None
    MatchLevel: Optional[str] = None
    Error: Optional['Error'] = None
    Debug: Optional[List[str]] = None

    def __post_init__(self):
        if self.TaxInfoItems is None:
            self.TaxInfoItems = []
        if self.Debug is None:
            self.Debug = []

    def __str__(self) -> str:
        tax_info_string = '; '.join(str(tax_info) for tax_info in self.TaxInfoItems) if self.TaxInfoItems else 'None'
        debug_string = ', '.join(self.Debug) if self.Debug else 'None'
        error = str(self.Error) if self.Error else 'None'
        return (f"GetBestMatchResponse: TaxInfoItems=[{tax_info_string}], MatchLevel={self.MatchLevel}, "
                f"Error={error}, Debug=[{debug_string}]")

FastTax NodeJS Rest Code Snippet

import axios from 'axios';
import querystring from 'querystring';
import { GetBestMatchResponse } from './ft_response.js';

/**
 * @constant
 * @type {string}
 * @description The base URL for the live ServiceObjects FastTax (FT) API service.
 */
const LiveBaseUrl = 'https://sws.serviceobjects.com/ft/web.svc/json/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the backup ServiceObjects FastTax (FT) API service.
 */
const BackupBaseUrl = 'https://swsbackup.serviceobjects.com/ft/web.svc/json/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the trial ServiceObjects FastTax (FT) API service.
 */
const TrialBaseUrl = 'https://trial.serviceobjects.com/ft/web.svc/json/';

/**
 * <summary>
 * Checks if a response from the API is valid by verifying that it either has no Error object
 * or the Error.Number is not equal to '4'.
 * </summary>
 * <param name="response" type="Object">The API response object to validate.</param>
 * <returns type="boolean">True if the response is valid, false otherwise.</returns>
 */
const isValid = (response) => !response?.Error || response.Error.Number !== '4';

/**
 * <summary>
 * Constructs a full URL for the GetBestMatch API endpoint by combining the base URL
 * with query parameters derived from the input parameters.
 * </summary>
 * <param name="params" type="Object">An object containing all the input parameters.</param>
 * <param name="baseUrl" type="string">The base URL for the API service (live, backup, or trial).</param>
 * <returns type="string">The constructed URL with query parameters.</returns>
 */
const buildUrl = (params, baseUrl) =>
    `${baseUrl}GetBestMatch?${querystring.stringify(params)}`;

/**
 * <summary>
 * Performs an HTTP GET request to the specified URL with a given timeout.
 * </summary>
 * <param name="url" type="string">The URL to send the GET request to.</param>
 * <param name="timeoutSeconds" type="number">The timeout duration in seconds for the request.</param>
 * <returns type="Promise<GetBestMatchResponse>">A promise that resolves to a GetBestMatchResponse object containing the API response data.</returns>
 * <exception cref="Error">Thrown if the HTTP request fails, with a message detailing the error.</exception>
 */
const httpGet = async (url, timeoutSeconds) => {
    try {
        const response = await axios.get(url, { timeout: timeoutSeconds * 1000 });
        return new GetBestMatchResponse(response.data);
    } catch (error) {
        throw new Error(`HTTP request failed: ${error.message}`);
    }
};

/**
 * <summary>
 * Provides functionality to call the ServiceObjects FastTax (FT) API's GetBestMatch endpoint,
 * retrieving tax rate information (e.g., total tax rate, city, county, state rates) for a given US address
 * with fallback to a backup endpoint for reliability in live mode.
 * </summary>
 */
const GetBestMatchClient = {
    /**
     * <summary>
     * Asynchronously invokes the GetBestMatch API endpoint, attempting the primary endpoint
     * first and falling back to the backup if the response is invalid (Error.Number == '4') in live mode.
     * </summary>
     * @param {string} Address - Address line of the address to get tax rates for (e.g., "123 Main Street").
     * @param {string} Address2 - Secondary address line (e.g., "Apt 4B"). Optional.
     * @param {string} City - The city of the address (e.g., "New York"). Optional if zip is provided.
     * @param {string} State - The state of the address (e.g., "NY"). Optional if zip is provided.
     * @param {string} Zip - The ZIP code of the address. Optional if city and state are provided.
     * @param {string} TaxType - The type of tax to look for ("sales" or "use").
     * @param {string} LicenseKey - Your license key to use the service.
     * @param {boolean} isLive - Value to determine whether to use the live or trial servers.
     * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service.
     * @returns {Promise<GetBestMatchResponse>} - A promise that resolves to a GetBestMatchResponse object.
     */
    async invokeAsync(Address, Address2, City, State, Zip, TaxType, LicenseKey, isLive = true, timeoutSeconds = 15) {
        const params = {
            Address,
            Address2,
            City,
            State,
            Zip,
            TaxType,
            LicenseKey
        };

        const url = buildUrl(params, isLive ? LiveBaseUrl : TrialBaseUrl);
        let response = await httpGet(url, timeoutSeconds);

        if (isLive && !isValid(response)) {
            const fallbackUrl = buildUrl(params, BackupBaseUrl);
            const fallbackResponse = await httpGet(fallbackUrl, timeoutSeconds);
            return fallbackResponse;
        }
        return response;
    },

    /**
     * <summary>
     * Synchronously invokes the GetBestMatch API endpoint by wrapping the async call
     * and awaiting its result immediately.
     * </summary>
     * @param {string} Address - Address line of the address to get tax rates for (e.g., "123 Main Street").
     * @param {string} Address2 - Secondary address line (e.g., "Apt 4B"). Optional.
     * @param {string} City - The city of the address (e.g., "New York"). Optional if zip is provided.
     * @param {string} State - The state of the address (e.g., "NY"). Optional if zip is provided.
     * @param {string} Zip - The ZIP code of the address. Optional if city and state are provided.
     * @param {string} TaxType - The type of tax to look for ("sales" or "use").
     * @param {string} LicenseKey - Your license key to use the service.
     * @param {boolean} isLive - Value to determine whether to use the live or trial servers.
     * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service.
     * @returns {GetBestMatchResponse} - A GetBestMatchResponse object with tax rate details or an error.
     */
    invoke(Address, Address2, City, State, Zip, TaxType, LicenseKey, isLive = true, timeoutSeconds = 15) {
        return (async () => await this.invokeAsync(
            Address, Address2, City, State, Zip, TaxType, LicenseKey, isLive, timeoutSeconds
        ))();
    }
};

export { GetBestMatchClient, GetBestMatchResponse };


/**
 * Input parameters for the GetBestMatch API call.
 */
export class GetBestMatchInput {
    constructor(data = {}) {
        this.Address = data.Address;
        this.Address2 = data.Address2;
        this.City = data.City;
        this.State = data.State;
        this.Zip = data.Zip;
        this.TaxType = data.TaxType;
        this.LicenseKey = data.LicenseKey;
        this.IsLive = data.IsLive !== undefined ? data.IsLive : true;
        this.TimeoutSeconds = data.TimeoutSeconds !== undefined ? data.TimeoutSeconds : 15;
    }

    toString() {
        return `GetBestMatchInput: Address = ${this.Address}, Address2 = ${this.Address2}, City = ${this.City}, State = ${this.State}, Zip = ${this.Zip}, TaxType = ${this.TaxType}, LicenseKey = ${this.LicenseKey}, IsLive = ${this.IsLive}, TimeoutSeconds = ${this.TimeoutSeconds}`;
    }
}

/**
 * Information Component for API responses.
 */
export class InformationComponent {
    constructor(data = {}) {
        this.Name = data.Name;
        this.Value = data.Value;
    }

    toString() {
        return `Name = ${this.Name}, Value = ${this.Value}`;
    }
}

/**
 * Error object for API responses.
 */
export class Error {
    constructor(data = {}) {
        this.Desc = data.Desc;
        this.Number = data.Number;
        this.Location = data.Location;
    }

    toString() {
        return `Error: Desc = ${this.Desc}, Number = ${this.Number}, Location = ${this.Location}`;
    }
}

/**
 * Tax information for a matched address or ZIP code.
 */
export class BestMatchTaxInfo {
    constructor(data = {}) {
        this.Zip = data.Zip;
        this.City = data.City;
        this.County = data.County;
        this.StateAbbreviation = data.StateAbbreviation;
        this.StateName = data.StateName;
        this.TaxRate = data.TaxRate;
        this.StateRate = data.StateRate;
        this.CityRate = data.CityRate;
        this.CountyRate = data.CountyRate;
        this.CountyDistrictRate = data.CountyDistrictRate;
        this.CityDistrictRate = data.CityDistrictRate;
        this.SpecialDistrictRate = data.SpecialDistrictRate;
        this.InformationComponents = (data.InformationComponents || []).map(component => new InformationComponent(component));
        this.TotalTaxExempt = data.TotalTaxExempt;
        this.NotesCodes = data.NotesCodes;
        this.NotesDesc = data.NotesDesc;
    }

    toString() {
        const componentsString = this.InformationComponents.length
            ? this.InformationComponents.map(component => component.toString()).join(', ')
            : 'null';
        return `BestMatchTaxInfo: Zip = ${this.Zip}, City = ${this.City}, County = ${this.County}, StateAbbreviation = ${this.StateAbbreviation}, StateName = ${this.StateName}, TaxRate = ${this.TaxRate}, StateRate = ${this.StateRate}, CityRate = ${this.CityRate}, CountyRate = ${this.CountyRate}, CountyDistrictRate = ${this.CountyDistrictRate}, CityDistrictRate = ${this.CityDistrictRate}, SpecialDistrictRate = ${this.SpecialDistrictRate}, InformationComponents = [${componentsString}], TotalTaxExempt = ${this.TotalTaxExempt}, NotesCodes = ${this.NotesCodes}, NotesDesc = ${this.NotesDesc}`;
    }
}

/**
 * Response from GetBestMatch API, containing tax rate information for the best match.
 */
export class GetBestMatchResponse {
    constructor(data = {}) {
        this.TaxInfoItems = (data.TaxInfoItems || []).map(taxInfo => new BestMatchTaxInfo(taxInfo));
        this.MatchLevel = data.MatchLevel;
        this.Error = data.Error ? new Error(data.Error) : null;
        this.Debug = data.Debug || [];
    }

    toString() {
        const taxInfoItemsString = this.TaxInfoItems.length
            ? this.TaxInfoItems.map(taxInfo => taxInfo.toString()).join('; ')
            : 'null';
        const debugString = this.Debug.length
            ? this.Debug.join(', ')
            : 'null';
        return `GetBestMatchResponse: TaxInfoItems = [${taxInfoItemsString}], MatchLevel = ${this.MatchLevel}, Error = ${this.Error ? this.Error.toString() : 'null'}, Debug = [${debugString}]`;
    }
}

export default GetBestMatchResponse;