Extending MojoPortal's Form Wizard Pro to Support Authorize.NET SIM

Posted by Shaun Geisert Friday, November 25, 2011 10:59:00 AM Categories: ASP.NET WebForms MojoPortal


NOTE:  I have updated my code to instead NOT utilize a separate controller for simplicity's sake (mainly just to keep everything self-contained).

Recently I thought it would be useful to be able to build out e-commerce forms directly within mojoPortal's FWP.  That way, a client can have their own custom forms that would collect the info they need, then they'd be able to define whatever product/prices/quantities that could then be purchased using Authorize.NET's SIM implementation.

Obviously, if your situation allows you to use Authorize.NET AIM (in which you can collect credit card info directly on your site), then you're much better off using mojo's Web Store module.  However, at CSU, we cannot utilize the AIM implementation and instead must pass off e-commerce transactions directly to Authorize.NET.  Additionally, if you don't need to collect any custom info it would be better to modify the mojoPortal solution code to directly support Authorize.NET SIM using the Web Store module (if I get time I'll also create/post this approach).

In the current implementation of the code, I opted to create a FWP submission handler which interfaces with a "controller" application that receives the redirects from the handler (via a querystring). 

In any case, without further ado, here's more info about the form submission event handler that was created for use with FWP.  Or, jump to the demonstration.

Features/Goals of Handler

  • Create a FWP submission handler generic enough to handle (and thus replace) the 6 e-commerce applications I currently manage
  • Support multiple products/prices, and allow these products/prices to be in any order and location within the form.  Pricing/product values are "squirreled" away in mojo's instruction block questions (since they are hard-coded and not input values)
  • Interface with a separate controller .NET app to handle the auth net form post (see below).
  • Encrypt/hash some, but not all querystring parameters as well as make them tamper-proof.  Eg, the auth net login id, transaction key, datevalid, and amount
  • Append a datetime to the querystring that would allow the url to only be valid for a short period of time.  This date cannot be modified by a savvy user.
  • Generate an e-mail to the individual completing the form
  • Give the ability for product title/prices to be defined within the instruction block of a FWP question
  • Provide a means to specify an invoice prefix for all transactions generated by the instance of FWP

Features/Goals of Controller

  • Taking a url with hashed/encrypted, tamper-proof parameters, read/decrypt querystring parameters and then format/forward requests to Authorize.NET in the following general fashion
  • Additional security measures, as specified below

Security

This implementation has the following levels of security:

  • FWP's built-in anti-spam, in which the user must pass some captcha test (if enabled)
  • The controller ensures that all requests are served over SSL
  • The controller checks that a valid referrer initiated the request (ie, a colostate domain).  This way, you can't directly bookmark/navigate to the generated url.
  • Check that the required querystring parameters have been defined
  • Check that the tamper-proof parameters haven't been tampered with
  • Decrypt the encrypted/hashed parameters.  This requires utilizing a secret hash that both the submission handler and the controller have access to (ie, for the program to work, both the handler and the controller must reference some identical hash value)
  • Check that the request has an expiration date and that the date is valid.  In other words, the url will have some unalterable date which must be in the future in order to proceed with the transaction.  I currently just set that value to be 1 minute in the future.
  • When an error does occur, be intentionally vague about what happened (of course, if a hacker sees this blog they will have some idea about what's going on behind the scenes.  However, they still can't tamper with the querystring parameters, nor decrypt them without the hash). 

Developer Implementation

  1. Download the submission handler solution file, compile it, and take the accompanying MojoFormSubmissionHandler.dll and HtmlAgilityPack.dll and place it in the bin folder of your site (my project utilizes .NET 4.0, btw).  Or, edit the solution to meet your needs. Also create the accompanying .config file.  For more info on how to do this see http://www.mojoportal.com/implementing-a-custom-form-submission-handler.aspx.
  2. Place the following keys in your mojoPortal user.config file:

            <add key="AuthorizeNetControllerUrl" value="https://url_to_your_controller_app.aspx" />
    <add key="ConfirmationEmailFromAddress" value="your_from_email_address" />
    <add key="Site1-AuthorizeNetProductionSIMAPILogin" value="authnet_login_id" />
    <add key="Site1-AuthorizeNetProductionSIMAPITransactionKey" value="authnet_transaction_key" />
    <add key="AuthNetSalt" value="some_8_character_salt" /> <!-- Eg, use http://www.pctools.com/guides/password/ to generate random salt value -->
     
  3. Download/publish the controller solution file (unless you are a CSU web developer and instead want to utilize what I've already created, in which case, you'll need to contact me for the "salt phrase")
  4. Finally, from within your FWP module's settings, you'll need to select the newly-added submission handler 

End-User Implementation

Once the form submission handler's already been "installed" to your site, here's how to configure your form.  To be safe, it is preferable to have your resident web developer perform the following steps.  To expedite setup, you can download this .config file as a template for building your form. Or, you can view a basic demo at http://ocl.colostate.edu/sandbox.  For a more complex example (which includes some jQuery to calculate totals directly on the form page), see this .config file, or the demo at http://www.ocl.colostate.edu/student-handbook

  1. Go to your form's settings.  Under Submission Event Handler, select MojoAuthorizeNET_SIMFormSubmissionHandler
  2. If you want your auto-generated invoice numbers to have a custom prefix (recommended), define that prefix under custom CSS class.  Eg, "VET_RMC_"
  3. If you want the auto-generated confirmation e-mail to have a custom subject, define that subject under "Label for Email List" under Notification Settings.
  4. Next create the questions for your form. In order for the handler to work, you need to be especially sure you've done the following:
  5. Create one or more instruction block questions that contain your product/price info.  Each product/price must be wrapped in an html tag with an id containing either "product" or "price".  Eg, <span id="productREDSHOES">Product Title</span>$<span id="priceREDSHOES">25.00</span>. BE VERY SURE THAT THE PRODUCT NAMES MATCH (EG, REDSHOES)
  6. Create the appropriate number of quantity textboxes to match the number of products/prices you defined in the instruction block (eg, if you have 3 products, you'll obviously need 3 quantity boxes).  Make sure that each of your quantity questions includes the word "quantity" in either the question alias or question name, and then your unique product name.  Eg, quantityREDSHOES, quantityBLUESHOES, quantityGREENSHOES, etc.  These obviously need to correspond with your product/prices.
  7. Now you're done.  Be sure to thoroughly test your form to ensure it works to your satisfaction.

Code


 

using System;
using System.Web;
using System.Collections.Generic;
using System.Text;
using log4net;
using sts.Business;
using mojoPortal.Business;
using sts.FormWizard.Web.UI;
using mojoPortal.Web;
using mojoPortal.Net;
using HtmlAgilityPack;
using System.Configuration;


namespace MojoFormSubmissionAuthNetHandler
{

    class MyHandler : FormSubmissionHandlerProvider
    {
        #region "Attributes"
        //******************************************************************
        //Attributes/Fields + Module-level Constants+Variables
        //******************************************************************

        private static readonly ILog log = LogManager.GetLogger(typeof(MyHandler));

        //authnet constants from user.config
        private string _loginID = ConfigurationManager.AppSettings["Site1-AuthorizeNetProductionSIMAPILogin"];
        private string _transactionKey = ConfigurationManager.AppSettings["Site1-AuthorizeNetProductionSIMAPITransactionKey"];
        private string _eCommerceUrl = ConfigurationManager.AppSettings["AuthorizeNetControllerUrl"];
        private string _confirmationFromAddress = ConfigurationManager.AppSettings["ConfirmationEmailFromAddress"];
        private string _salt = ConfigurationManager.AppSettings["AuthNetSalt"]; //MUST MATCH THE SALT VALUE USED AT E-COMMERCE CONTROLLER.

        //e-mail properties
        private string _subject = "Form Submission Received: ";

        //attributes
        FormSubmission _payment = new FormSubmission();
        SortedDictionary<String, String> _products = new SortedDictionary<String, String>();
        SortedDictionary<string, Decimal> _prices = new SortedDictionary<string, Decimal>();
        SortedDictionary<string, int> _quantities = new SortedDictionary<string, int>();

        SiteSettings _site = new SiteSettings();

        #endregion //Attributes

        #region "Constructors"
        //******************************************************************
        //Constructors
        //******************************************************************

        public MyHandler()
        { }

        #endregion //Constructors

        #region "Get/Set Methods"
        //******************************************************************
        //Get/Set Methods
        //******************************************************************

        public string LoginId
        {
            get
            {
                return Utils.Encrypt(_loginID);
                //return QueryStringModule.Encrypt(_loginID);
            }
        }

        public string TransactionKey
        {
            get
            {
                return Utils.Encrypt(_transactionKey);
                //return QueryStringModule.Encrypt(_transactionKey);
            }
        }

        //public string DateValid
        //{
        //    get
        //    {
        //        return HttpUtility.UrlEncode(_payment.DateValid.ToString());
        //    }
        //}

        #endregion //Get/Set Methods

        #region "Event Procedures"
        //******************************************************************
        //Event Procedures
        //******************************************************************

        public override void FormSubmittedEventHandler(object sender, FormSubmissionEventArgs e)
        {

            if (e == null) return;
            if (e.ResponseSet == null) return;

            log.Info("MojoAuthorizeNETFormSubmissionHandlerProvider called");

            StringBuilder results = new StringBuilder();

            //Note about e-mail
            results.Append("NOTE: This e-mail is a confirmation of your form submission ONLY.  Your registration isn't complete until your online payment has been accepted.");
            results.Append("\r\n");
            results.Append("\r\n");

            //how to get the site user if the user was authenticated
            if (e.User != null)
            {
                results.Append("submitted by user: " + e.User.Name);
                results.Append("\r\n");
            }

            //how to get the questions and answers
            List<WebFormQuestion> questionList = WebFormQuestion.GetByForm(e.ResponseSet.FormGuid);
            List<WebFormResponse> responses = WebFormResponse.GetByResponseSet(e.ResponseSet.Guid);

            //for each question
            foreach (WebFormQuestion question in questionList)
            {
                string response = string.Empty;

                //see if we have an instruction block (which should contain our products/prices
                if (question.QuestionTypeId == 8)
                {
                    HtmlDocument doc = new HtmlDocument();

                    //load the html
                    doc.LoadHtml(question.QuestionText);

                    //within the instruction block, parse out values contained in tags with
                    //ids containing "product" or "price".  Eg, <span id="product1">

                    try // to populate product info collected from instruction block(s)
                    {
                        // if products already is non-empty
                        if (_products.Count > 0)
                        {
                            // make a copy of the dictionary
                            var copy = _products;

                            // do normal assigning of dictionary
                            _products = Utils.ExtractProductByTag(doc, "//*[contains(@id, '" + QuestionAlias.Product + "')]");

                            // merge dictionaries
                            _products = _products.Merge(copy);
                        }
                        else
                        {
                            _products = Utils.ExtractProductByTag(doc, "//*[contains(@id, '" + QuestionAlias.Product + "')]");
                        }
                    }
                    catch (Exception)
                    {
                        log.Info("dealing with an instruction block that didn't include a product");
                        continue;
                    }

                    try // to populate price info collected from instruction block
                    {
                        // if products already is non-empty
                        if (_prices.Count > 0)
                        {
                            // make a copy of the dictionary
                            var copy = _prices;

                            // do normal assigning of dictionary
                            _prices = Utils.ExtractPriceByTag(doc, "//*[contains(@id, '" + QuestionAlias.Price + "')]");

                            // merge dictionaries
                            _prices = _prices.Merge(copy);
                        }
                        else
                        {
                            _prices = Utils.ExtractPriceByTag(doc, "//*[contains(@id, '" + QuestionAlias.Price + "')]");
                        }
                    }
                    catch (Exception)
                    {
                        log.Info("dealing with an instruction block that didn't include a price");
                        continue;
                    }

                    continue; //skip adding instruction block

                }
                else //get the actual response
                {
                    response = GetResponse(e.ResponseSet.Guid, question.Guid, responses);
                }

                //populate fields for authorize.net
                ExtractAuthNetFields(question, response);

                //append question text to results for e-mail (if there is a response):
                if (!string.IsNullOrEmpty(response))
                {
                    results.Append("\r\n" + question.QuestionText + "\r\n");
                    results.Append(response);
                    results.Append("\r\n");
                }

            }


            //ensure that product / price lists are of equal size
            if ((_products.Count == _prices.Count))
            {
                log.Info("Yay! Products and Price counts are equal");

                try
                {
                    //Log products dictionary for debugging
                    foreach (KeyValuePair<string, string> kvProductTitlePair in _products)
                    {
                        log.Info("kvProductTitlePair.Key: " + kvProductTitlePair.Key + " | kvProductTitlePair.Value: " + kvProductTitlePair.Value);
                    }
                }
                catch (Exception)
                {
                    log.Info("Can't iterate through products dictionary");
                }

                try
                {
                    //Log quantity dictionary for debugging
                    foreach (KeyValuePair<string, int> kvQtyPair in _quantities)
                    {
                        log.Info("kvQtyPair.Key: " + kvQtyPair.Key + " | kvQtyPair.Value: " + kvQtyPair.Value);
                    }
                }
                catch (Exception)
                {
                    log.Info("Can't iterate through quantities dictionary");
                }

                try
                {
                    //Log prices dictionary for debugging
                    foreach (KeyValuePair<string, Decimal> kvPricePair in _prices)
                    {
                        log.Info("kvPricePair.Key: " + kvPricePair.Key + " | kvPricePair.Value: " + kvPricePair.Value);
                    }
                }
                catch (Exception)
                {
                    log.Info("Can't iterate through prices dictionary");
                }

                //instantiate vars
                List<Product> products = new List<Product>();
                Decimal totalAmount = 0;

                try // to populate product info collected from instruction block
                {

                    foreach (KeyValuePair<string, int> kvpPair in _quantities)
                    {
                        //break loop if quantity is 0
                        if (kvpPair.Value == 0)
                        {
                            continue;
                        }

                        Product p = new Product();

                        string productName;
                        if (_products.TryGetValue(kvpPair.Key, out productName))
                        {
                            p.Title = productName;
                        }

                        Decimal productPrice;
                        if (_prices.TryGetValue(kvpPair.Key, out productPrice))
                        {
                            p.Price = productPrice;
                        }

                        p.Quantity = kvpPair.Value;
                        p.Total = (p.Quantity * p.Price);

                        products.Add(p);

                        log.Info("Product : " + p.Title + "|" + p.Price + "|" + p.Quantity + "|" + p.Total + "|");
                    }
                }
                catch (Exception ex)
                {
                    log.Info("An error occurred in populating product object: " + ex.Message.ToString());
                }




                try
                {
                    //build out form submission, including above products and total
                    _payment.Products = products;
                    _payment.IsTestRequest = false;
                    _payment.DateValid = DateTime.Now.AddMinutes(1);

                    //tabulate total amount of charges
                    foreach (Product p in products)
                    {
                        totalAmount += p.Total;
                    }

                    _payment.Amount = totalAmount;
                }
                catch (Exception ex)
                {
                    log.Info("An error occurred in amount total calculation method. " + ex.Message.ToString());
                }



                log.Info("Attempting to send e-mail");

                try //to send an email to person filling out form with the results 
                {
                    string fromAddress = _confirmationFromAddress;

                    if (string.IsNullOrEmpty(_confirmationFromAddress))
                        fromAddress = _site.DefaultEmailFromAddress;

                    string subject = _subject + e.Config.PickListLabel;
                    string msg = string.Empty;

                    //append total to msg, unless we think it already exists
                    if (!results.ToString().ToLower().Contains("total charges"))
                    {
                        msg = results.ToString() + "\r\nTotal Charges:\r\n" + totalAmount.ToString();
                    }
                    else //total charges are already displayed in the msg
                    {
                        msg = results.ToString();
                    }

                    Email.Send(
                            SiteUtils.GetSmtpSettings(),
                            fromAddress,
                            string.Empty,
                            string.Empty,
                            _payment.Email,
                            string.Empty,
                            string.Empty,
                            subject,
                            msg,
                            false,
                            Email.PriorityNormal);

                    log.Info(results.ToString());
                }
                catch (Exception ex)
                {
                    log.Info("An error occurred in e-mail method: " + ex.Message.ToString());
                }



                string securedUrl = string.Empty;

                try
                {


                    //Create new authorizeNET post.  Can't initiate new form post to authnet because of FWP's being contained in UpdatePanel
                    //AuthorizeNETPost authPost = new AuthorizeNETPost(payment, "https://secure.authorize.net/gateway/transact.dll", false);

                    //get invoice prefix
                    string invoice = string.Empty;

                    if (e.Config.CustomCssClass.Length > 0)
                        invoice = e.Config.CustomCssClass;

                    string nonTamperProofParams = string.Empty;

                    //build out non-tamperproof params
                    nonTamperProofParams = BuildParams(AuthNetField.FirstName.ToString(), _payment.FirstName, false);
                    nonTamperProofParams += BuildParams(AuthNetField.LastName.ToString(), _payment.LastName, false);
                    nonTamperProofParams += BuildParams(AuthNetField.Company.ToString(), _payment.Company, false);
                    nonTamperProofParams += BuildParams(AuthNetField.Address.ToString(), _payment.Address, false);
                    nonTamperProofParams += BuildParams(AuthNetField.City.ToString(), _payment.City, false);
                    nonTamperProofParams += BuildParams(AuthNetField.State.ToString(), _payment.State, false);
                    nonTamperProofParams += BuildParams(AuthNetField.Zip.ToString(), _payment.Zip, false);
                    nonTamperProofParams += BuildParams(AuthNetField.Country.ToString(), _payment.Country, false);
                    nonTamperProofParams += BuildParams(AuthNetField.Email.ToString(), _payment.Email, false);
                    nonTamperProofParams += BuildParams(AuthNetField.Phone.ToString(), _payment.Phone, false);
                    nonTamperProofParams += BuildParams(AuthNetField.Fax.ToString(), _payment.Fax, false);
                    nonTamperProofParams += BuildParams(CustomAuthNetField.InvoicePrefix.ToString(), invoice, false);
                    nonTamperProofParams += BuildParams(AuthNetField.IsTestRequest.ToString(), _payment.IsTestRequest.ToString(), true);

                    string tamperProofParams = string.Empty;

                    //build out tamper-proof params
                    tamperProofParams = BuildParams(AuthNetField.LoginId.ToString(), LoginId, false);
                    tamperProofParams += BuildParams(AuthNetField.TransactionKey.ToString(), TransactionKey, false);
                    tamperProofParams += BuildParams(AuthNetField.Amount.ToString(), _payment.Amount.ToString(), false);
                    tamperProofParams += BuildParams(CustomAuthNetField.DateValid.ToString(), _payment.DateValid.ToString(), true);

                    //create secured url
                    securedUrl = Utils.CreateTamperProofURL(_eCommerceUrl, nonTamperProofParams, tamperProofParams);

                }
                catch (Exception ex)
                {
                    //log.Info("An error occurred in authorize.net method: " + sf.GetMethod().ToString() + " at line #: " + sf.GetFileLineNumber().ToString());
                    log.Info("auth method error: " + ex.Message.ToString());
                }



                //oddly, the quantities variable seems to be persisted in memory, so to be safe I'll clear everything
                _products.Clear();
                _prices.Clear();
                _quantities.Clear();

                //Redirect to e-commerce controller
                if (!string.IsNullOrEmpty(securedUrl))
                    HttpContext.Current.Response.Redirect(securedUrl);
            }
            else
            {
                log.Info("Unequal product/price counts: Product Count:" + _products.Count +
                    " Quantity Count:" + _quantities.Count +
                    " Price Count:" + _prices.Count
                    );
            }
        }

        #endregion //Event Procedures

        #region "Behavioral Methods"
        //******************************************************************
        //Behavioral Methods
        //******************************************************************

        /// <summary>
        /// Builds url with querystring parameters
        /// </summary>
        /// <param name="qsName"></param>
        /// <param name="qsValue"></param>
        /// <param name="isEnd"></param>
        /// <returns></returns>
        private string BuildParams(string qsName, string qsValue, bool isEnd)
        {
            //do we have a param value
            if (!string.IsNullOrEmpty(qsValue))
            {
                if (!isEnd) //if not the end of the url
                    return qsName + "=" + qsValue + "&";
                else
                    return qsName + "=" + qsValue;
            }
            else //no param value
            {
                return string.Empty;
            }
        }


        /// <summary>
        /// Method designed to find FWP form fields containing relevant authorize.NET information using FWP question / question aliases.
        /// </summary>
        /// <param name="question"></param>
        /// <param name="response"></param>
        private void ExtractAuthNetFields(WebFormQuestion question, string response)
        {
            try // to extract first name
            {
                if (QuestionMatchesFieldName(question, AuthNet.FirstName, QuestionAlias.FirstName))
                {
                    _payment.FirstName = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get first name");
            }


            try //to extract last name
            {
                if (QuestionMatchesFieldName(question, AuthNet.LastName, QuestionAlias.LastName))
                {
                    _payment.LastName = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get last name");
            }


            try //to extract company
            {
                if (QuestionMatchesFieldName(question, AuthNet.Company, QuestionAlias.Company))
                {
                    _payment.Company = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get company");
            }


            try //to extract address
            {
                if (QuestionMatchesFieldName(question, AuthNet.Address, QuestionAlias.Address))
                {
                    _payment.Address = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the address");
            }


            try //to extract city
            {
                if (QuestionMatchesFieldName(question, AuthNet.City, QuestionAlias.City))
                {
                    _payment.City = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the city");
            }


            try //to extract state
            {
                if (QuestionMatchesFieldName(question, AuthNet.State, QuestionAlias.State))
                {
                    _payment.State = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the state");
            }


            try //to extract zip code
            {
                if (QuestionMatchesFieldName(question, AuthNet.Zip, QuestionAlias.Zip))
                {
                    _payment.Zip = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the zip code");
            }


            try //to extract country
            {
                if (QuestionMatchesFieldName(question, AuthNet.Country, QuestionAlias.Country))
                {
                    _payment.Country = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the country");
            }


            try //to extract e-mail alias
            {
                if (QuestionMatchesFieldName(question, AuthNet.Email, QuestionAlias.Email))
                {
                    _payment.Email = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the email");
            }


            try //to extract phone alias
            {
                if (QuestionMatchesFieldName(question, AuthNet.Phone, QuestionAlias.Phone))
                {
                    _payment.Phone = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the phone");
            }



            try //to extract fax alias
            {
                if (QuestionMatchesFieldName(question, AuthNet.Fax, QuestionAlias.Fax))
                {
                    _payment.Fax = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get the phone");
            }


            try //to extract comments/description
            {
                if (QuestionMatchesFieldName(question, AuthNet.Description, QuestionAlias.Description))
                {
                    _payment.Description = response;
                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get description");
            }


            try // to extract quantities
            {
                //just setting duplicate params bc only questionalias is valid
                if (QuestionMatchesFieldName(question, "justignoremepleaseimnotneeded", QuestionAlias.Quantity))
                {
                    int qty = 0;
                    log.Info("Parsed Question Alias: " + question.QuestionAlias.Replace(QuestionAlias.Quantity, "") + " Response: " + response);

                    if (!string.IsNullOrEmpty(response))
                    {
                        //this is a quantity value, so it better be numeric
                        qty = Int32.Parse(Utils.RemoveNonAlphaFromString(response));
                    }

                    _quantities.Add(question.QuestionAlias.Replace(QuestionAlias.Quantity, ""), qty);

                }
            }
            catch (Exception)
            {
                log.Info("An error occurred in trying to get quantity");
            }
        }

        /// <summary>
        /// Helper method to match up fwp questions/question aliases with our predefined field names
        /// </summary>
        /// <param name="question"></param>
        /// <param name="authField"></param>
        /// <param name="aliasField"></param>
        /// <returns></returns>
        private bool QuestionMatchesFieldName(WebFormQuestion question, string authField, string aliasField)
        {
            string alias = question.QuestionAlias.ToLower();
            string questionText = Utils.RemoveNonAlphaFromString(question.QuestionText.ToLower());

            //log.Info(questionText + " " + alias);

            if (!string.IsNullOrEmpty(alias))
            {
                if ((
                    alias.Contains(authField) ||
                    alias.Contains(aliasField)
                   ))
                    return true;
                //else
                //    return false;
            }


            if (!string.IsNullOrEmpty(questionText))
            {
                if ((

                questionText.Contains(authField) ||
                questionText.Contains(aliasField
                )))
                    return true;
                else
                    return false;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Method to retrieve FWP response
        /// </summary>
        /// <param name="responseSetGuid"></param>
        /// <param name="questionGuid"></param>
        /// <param name="responses"></param>
        /// <returns></returns>
        private string GetResponse(Guid responseSetGuid, Guid questionGuid, List<WebFormResponse> responses)
        {
            foreach (WebFormResponse response in responses)
            {
                if (
                    (response.ResponseSetGuid == responseSetGuid)
                    && (response.QuestionGuid == questionGuid)
                    )
                {
                    return response.Response;
                }
            }

            return string.Empty;
        }

        #endregion //Behavioral Methods

    }
}