GeoPhone C# Code Snippet

using System;
using GPService;

namespace geophone_dot_net.SOAP
{
    /// <summary>
    /// Provides functionality to call the ServiceObjects GeoPhone SOAP service's GetPhoneInfo_V2 operation,
    /// retrieving phone-related information (e.g., provider and contact details) with fallback to a backup endpoint
    /// for reliability in live mode.
    /// </summary>
    public class GetPhoneInfoValidation
    {
        private const string LiveBaseUrl = "https://sws.serviceobjects.com/GP/SOAP.svc/SOAP";
        private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/GP/SOAP.svc/SOAP";
        private const string TrailBaseUrl = "https://trial.serviceobjects.com/GP/SOAP.svc/SOAP";

        private readonly string _primaryUrl;
        private readonly string _backupUrl;
        private readonly int _timeoutMs;
        private readonly bool _isLive;

        /// <summary>
        /// Initializes URLs/timeout/IsLive.
        /// </summary>
        public GetPhoneInfoValidation(bool isLive)
        {
            // Read timeout (milliseconds) and IsLive flag
            _timeoutMs = 10000;
            _isLive = isLive;

            if (_isLive)
            {
                _primaryUrl = LiveBaseUrl;
                _backupUrl = BackupBaseUrl;
            }
            else
            {
                _primaryUrl = TrailBaseUrl;
                _backupUrl = TrailBaseUrl;
            }

            if (string.IsNullOrWhiteSpace(_primaryUrl))
                throw new InvalidOperationException("Primary URL not set.");
            if (string.IsNullOrWhiteSpace(_backupUrl))
                throw new InvalidOperationException("Backup URL not set.");
        }

        /// <summary>
        /// Asynchronously calls the GetPhoneInfo_V2 SOAP endpoint to retrieve phone information,
        /// attempting the primary endpoint first and falling back to the backup if the response is invalid
        /// (Error.Number == "4") in live mode or if the primary call fails.
        /// </summary>
        /// <param name="phoneNumber">The phone number in question.</param>
        /// <param name="licenseKey">Your ServiceObjects GeoPhone license key</param>
        /// <returns>A <see cref="Task{TResult}"/> containing a <see cref="PhoneInfo_V2"/> object with provider and contact details or an error.</returns>
        /// <exception cref="Exception">
        /// Thrown if both primary and backup endpoints fail.
        /// </exception>
        public async Task<PhoneInfo_V2> InvokeAsync(string phoneNumber, string licenseKey)
        {
            SOAPClient clientPrimary = null;
            SOAPClient clientBackup = null;

            try
            {
                // Attempt Primary_PLL
                clientPrimary = new SOAPClient();
                clientPrimary.Endpoint.Address = new System.ServiceModel.EndpointAddress(_primaryUrl);
                clientPrimary.InnerChannel.OperationTimeout = TimeSpan.FromMilliseconds(_timeoutMs);

                PhoneInfo_V2 response = await clientPrimary.GetPhoneInfo_V2Async(phoneNumber, licenseKey).ConfigureAwait(false);

                if(response == null || (response.Error != null && response.Error.Number == "4"))
                {
                    throw new InvalidOperationException("Primary endpoint returned null or a fatal Number=4 error for PhoneInfo_V2");
                }
                return response;
            }
            catch (Exception primaryEx)
            {
                // If primary fails, try Backup
                try
                {
                    clientBackup =new SOAPClient();
                    clientBackup.Endpoint.Address = new System.ServiceModel.EndpointAddress(_backupUrl);
                    clientBackup.InnerChannel.OperationTimeout = TimeSpan.FromMilliseconds(_timeoutMs);

                    PhoneInfo_V2 response = await clientBackup.GetPhoneInfo_V2Async(phoneNumber, licenseKey).ConfigureAwait(false);
                    return response;
                }
                catch (Exception backupEx)
                {
                    // If backup also fails, wrap both exceptions
                    throw new Exception(
                        $"Both primary and backup endpoints failed.\n" +
                        $"Primary error: {primaryEx.Message}\n" +
                        $"Backup error: {backupEx.Message}");
                }
                finally
                {
                    clientBackup?.Close();
                }
            }
            
            finally
            {
                clientPrimary?.Close();
            }
        }
    }
}

GeoPhone Python Code Snippet

from suds.client import Client
from suds import WebFault
from suds.sudsobject import Object

class GetPhoneInfoValidation:
    def __init__(self, license_key: str, is_live: bool, timeout_ms: int = 10000):
        """
        license_key: Service Objects GeoPhone license key.
        is_live: whether to use live or trial endpoints
        timeout_ms: SOAP call timeout in milliseconds
        """
        self._timeout_s = timeout_ms / 1000.0
        self._is_live = is_live
        self.license_key = license_key

        # WSDL URLs
        self._primary_wsdl = (
            "https://sws.serviceobjects.com/gp/soap.svc?wsdl"
            if is_live else
            "https://trial.serviceobjects.com/gp/soap.svc?wsdl"
        )
        self._backup_wsdl = (
            "https://swsbackup.serviceobjects.com/gp/soap.svc?wsdl"
            if is_live else
            "https://trial.serviceobjects.com/gp/soap.svc?wsdl"
        )

    def get_phone_info(self, phone_number: str) -> Object:
        """
        Calls GetPhoneInfo_V2 on the primary endpoint; on None response,
        WebFault, or Error.Number == '4' falls back to the backup endpoint.
        returns: the suds response object
        raises RuntimeError: if both endpoints fail
        """
        # Common kwargs for both calls
        call_kwargs = dict(
            PhoneNumber=phone_number,
            LicenseKey=self.license_key
        )

        # Attempt primary
        try:
            client = Client(self._primary_wsdl, timeout=self._timeout_s)

            # Override endpoint URL if needed:
            response = client.service.GetPhoneInfo_V2(**call_kwargs)

            # If response is None or fatal error code, trigger fallback
            if response is None or (hasattr(response, 'Error') and response.Error and response.Error.Number == '4'):
                raise ValueError("Primary returned no result or fatal Error.Number=4")

            return response

        except (WebFault, ValueError, Exception) as primary_ex:
            # Attempt backup
            try:
                client = Client(self._backup_wsdl, timeout=self._timeout_s)
                response = client.service.GetPhoneInfo_V2(**call_kwargs)
                if response is None:
                    raise ValueError("Backup returned no result")
                return response
            except (WebFault, Exception) as backup_ex:
                msg = (
                    "Both primary and backup endpoints failed.\n"
                    f"Primary error: {str(primary_ex)}\n"
                    f"Backup error: {str(backup_ex)}"
                )
                raise RuntimeError(msg)

GeoPhone NodeJS Code Snippet

import { soap } from "strong-soap";

/**
 * <summary>
 * A class that provides functionality to call the ServiceObjects GeoPhone SOAP service's GetPhoneInfo_V2 endpoint,
 * retrieving phone-related information with fallback to a backup endpoint for reliability in live mode.
 * </summary>
 */
class GetPhoneInfoSoap {
    /**
     * <summary>
     * Initializes a new instance of the GetPhoneInfoSoap class with the provided input parameters,
     * setting up primary and backup WSDL URLs based on the live/trial mode.
     * </summary>
     * <param name="input" type="Object">The input object containing phoneNumber, licenseKey, isLive, and timeoutSeconds.</param>
     * <exception cref="Error">Thrown if phoneNumber, licenseKey, primaryWsdl, or backupWsdl is empty or null.</exception>
     */
    constructor(phoneNumber, licenseKey, isLive, timeoutSeconds) {
        if (!phoneNumber) throw new Error("PhoneNumber cannot be empty or null.");
        if (!licenseKey) throw new Error("LicenseKey cannot be empty or null.");

        this.phoneNumber = phoneNumber;
        this.licenseKey = licenseKey;
        this.isLive = isLive;
        this.timeoutSeconds = timeoutSeconds;
        this.liveBaseUrl = "https://sws.serviceobjects.com/gp/soap.svc?wsdl";
        this.backupBaseUrl = "https://swsbackup.serviceobjects.com/gp/soap.svc?wsdl";
        this.trailBaseUrl = "https://trial.serviceobjects.com/gp/soap.svc?wsdl";
        this._primaryWsdl = this.isLive ? this.liveBaseUrl : this.trailBaseUrl;
        this._backupWsdl = this.isLive ? this.backupBaseUrl : this.trailBaseUrl;
        if (!this._primaryWsdl) throw new Error("Primary WSDL URL is not set.");
        if (!this._backupWsdl) throw new Error("Backup WSDL URL is not set.");
    }

    /**
     * <summary>
     * Asynchronously calls the GetPhoneInfo_V2 SOAP endpoint, attempting the primary endpoint
     * first and falling back to the backup if the response is invalid (Error.Number == "4") in live mode
     * or if the primary call fails.
     * </summary>
     * <returns type="Promise<GPResponse>">A promise that resolves to a GPResponse object with provider and contact details or an error.</returns>
     * <exception cref="Error">Thrown if both primary and backup calls fail, with details of both errors.</exception>
     */
    async getPhoneInfo() {
        const args = {
            PhoneNumber: this.phoneNumber,
            LicenseKey: this.licenseKey,
        };

        try {
            const primaryResult = await this._callSoap(this._primaryWsdl, args);

            if (this._isLive && !this._isValid(primaryResult)) {
                console.warn(
                    "Primary returned Error.Number == '4', falling back to backup..."
                );
                const backupResult = await this._callSoap(this._backupWsdl, args);
                return backupResult;
            }

            return primaryResult;
        } catch (primaryErr) {
            try {
                const backupResult = await this._callSoap(this._backupWsdl, args);
                return backupResult;
            } catch (backupErr) {
                throw new Error(
                    `Both primary and backup calls failed:\nPrimary: ${primaryErr.message}\nBackup: ${backupErr.message}`
                );
            }
        }
    }

    /**
     * <summary>
     * Performs a SOAP service call to the specified WSDL URL with the given arguments,
     * creating a client and processing the response into a GPResponse object.
     * </summary>
     * <param name="wsdlUrl" type="string">The WSDL URL of the SOAP service endpoint (primary or backup).</param>
     * <param name="args" type="Object">The arguments to pass to the GetPhoneInfo_V2 method (e.g., PhoneNumber, LicenseKey).</param>
     * <returns type="Promise<GPResponse>">A promise that resolves to a GPResponse object containing the SOAP response data.</returns>
     * <exception cref="Error">Thrown if the SOAP client creation fails, the service call fails, or the response cannot be parsed.</exception>
     */
    _callSoap(wsdlUrl, args) {
        return new Promise((resolve, reject) => {
            soap.createClient(wsdlUrl, { timeout: this.timeoutSeconds * 1000 }, (err, client) => {
                if (err) return reject(err);

                client.GetPhoneInfo_V2(args, (err, result) => {
                    if (err) return reject(err);
                    const rawData = result.GetPhoneInfo_V2Result;
                    try {
                        if (!rawData) {
                            return reject(new Error("SOAP response is empty or undefined."));
                        }
                        resolve(rawData);
                    } catch (parseErr) {
                        reject(new Error(`Failed to parse SOAP response: ${parseErr.message}`));
                    }
                });
            });
        });
    }

    /**
     * <summary>
     * Checks if a SOAP response is valid by verifying that it exists and either has no Error object
     * or the Error.Number is not equal to '4'.
     * </summary>
     * <param name="response" type="GPResponse">The GPResponse object to validate.</param>
     * <returns type="boolean">True if the response is valid, false otherwise.</returns>
     */
    _isValid(response) {
        return response && (!response.Error || response.Error.Number !== "4");
    }
}

export { GetPhoneInfoSoap };