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

Posted by Shaun Geisert Thursday, May 31, 2012 5:17:00 PM

Download Visual Studio solution file

 

I see there have been some individuals who are interested in integrating Authorize.NET SIM with MojoPortal’s Form Wizard Pro.  To make this integration simpler, this is a means whereby you can accomplish this feat without a separate controller, ala what was originally described at http://code.colostate.edu/extending-mojoportals-form-wizard-pro-to-support-authorizenet-sim.aspx.

The documentation at the above url largely applies to the following code, except that you obviously can disregarding anything involving publishing a separate solution.  Additionally, you will need to remember to add/reference the AuthorizeNet.dll in your bin folder (available in the above solution file).

The main trick to making the following code work is that you have to disable partial page postbacks (ie, AJAX) on pages that contain an instance of an e-commerce FWP form.  Note that you should be careful about using the following code, since disabling partial rendering will interfere with the complete functioning of mojoPortal (eg, you won’t be able to edit the FWP module’s settings once the following code is in place.  So, first make sure your FWP module is set up appropriately before enabling the following code).

Sample layout.master code to disable partial page postbacks (obviously edit to meet your needs, such as specifying a specific module id instead of a module definition id):

<script runat="server">

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            int pageId = 0;
            
            try { pageId = int.Parse(Request.QueryString["pageid"]); }
            catch (Exception) { }
            
            if (PageContainFWPModule(pageId))
                ScriptManager1.EnablePartialRendering = false;
            else
                ScriptManager1.EnablePartialRendering = true;
            
        }
        
        private bool PageContainFWPModule(int pageId)
        {
            //this is one way of solving response.write within updatepanels.
            //see http://weblogs.asp.net/leftslipper/archive/2007/02/26/sys-webforms-pagerequestmanagerparsererrorexception-what-it-is-and-how-to-avoid-it.aspx for other solutions
            
            using (System.Data.IDataReader reader = Module.GetPageModules(pageId))
            {
                while (reader.Read())
                {
                    //you need to look up what the moduledefid is for FWP in the ModuleDefinitions table
                    if (Convert.ToInt32(reader["ModuleDefID"]) == 19)
                    {
                        return true;
                    }
                }
            }

            return false;
        }
        
        
    </script>

Sample User.Config file settings:

    <!-- E-commerce Settings -->
    <add key="AuthorizeNetPostUrl" value="https://secure.authorize.net/gateway/transact.dll" />
    <add key="AuthorizeNetRelayUrl" value="" />
    <add key="ConfirmationEmailFromAddress" value="your_email" />
    <add key="Site1-AuthorizeNetProductionSIMAPILogin" value="login" />
    <add key="Site1-AuthorizeNetProductionSIMAPITransactionKey" value="transaction_key" />
    <add key="AuthorizeNetIsTestRequest" value="true" />

 

Authorize.NET Form Submission Handler 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;
using System.Web.UI;
using System.Collections.Specialized;
using System.Reflection;


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 static readonly string _loginID = ConfigurationManager.AppSettings["Site1-AuthorizeNetProductionSIMAPILogin"];
        private static readonly string _transactionKey = ConfigurationManager.AppSettings["Site1-AuthorizeNetProductionSIMAPITransactionKey"];
        private static readonly string _authNetPostUrl = ConfigurationManager.AppSettings["AuthorizeNetPostUrl"];
        private static readonly string _authNetRelayUrl = ConfigurationManager.AppSettings["AuthorizeNetRelayUrl"];
        private static readonly bool _isTestRequest = Boolean.Parse(ConfigurationManager.AppSettings["AuthorizeNetIsTestRequest"]);
        private static readonly string _confirmationFromAddress = ConfigurationManager.AppSettings["ConfirmationEmailFromAddress"];

        //e-mail properties
        private static readonly string _emailAlias = "email";
        private string _prependSubject = "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>();

        private static readonly string _productAlias = "product";
        private static readonly string _priceAlias = "price";
        private static readonly string _quantityAlias = "quantity";

        SiteSettings _site = new SiteSettings();

        #endregion //Attributes

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

        public MyHandler()
        { }

        #endregion //Constructors

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

        #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 emailText = new StringBuilder();

            //Note about e-mail
            emailText.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.");
            emailText.Append("\r\n");
            emailText.Append("\r\n");

            //how to get the site user if the user was authenticated
            if (e.User != null)
            {
                emailText.Append("submitted by user: " + e.User.Name);
                emailText.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)
                {
                    TryToExtractProductPricesFromInstructionBlock(question);
                }
                else //not an instruction, so get the actual response
                {
                    response = GetResponse(e.ResponseSet.Guid, question.Guid, responses);
                }

                //if there is a question alias
                if (!string.IsNullOrEmpty(question.QuestionAlias))
                {
                    // use reflection to iterate through the properties of authorize.net class and pair matched question aliases
                    PairAuthNetValuesWithFormValues(question, response);
                }

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

            }


            //ensure that product / price lists are of equal size
            if ((_products.Count == _prices.Count))
            {
                //Build totals
                Decimal totalAmount = BuildTotals();

                //Builds/sends e-mail
                BuildEmail(emailText, e.Config.PickListLabel, totalAmount);

                try //to initiate authorize.net form post
                {
                    AuthorizeNetPost.InitiateAuthNetPost(_authNetPostUrl, _authNetRelayUrl, _loginID, _transactionKey, totalAmount, _isTestRequest);
                }
                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();
                AuthorizeNetPost.Close();


            }
            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
        //******************************************************************

        private void TryToExtractProductPricesFromInstructionBlock(WebFormQuestion question)
        {
            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, '" + _productAlias + "')]");

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

            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, '" + _priceAlias + "')]");

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

            return; //skip adding instruction block
        }

        private void PairAuthNetValuesWithFormValues(WebFormQuestion question, string response)
        {

            // use reflection to iterate through the properties of authorize.net class
            AuthorizeNetPost p = new AuthorizeNetPost();

            //first see if the question contains a quantity value.  
            if (question.QuestionAlias.Contains(_quantityAlias))
            {
                //It does, so add it to our quantity dictionary
                BuildQuantities(question, response);
            }

            //check if the question is an e-mail address (for sending out a confirmation e-mail)
            if (question.QuestionAlias.Contains(_emailAlias))
            {
                _payment.Email = response;
            }

            Type type = p.GetType();
            PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Static);

            //For each public static field in authorize.net, see if we have a match against our question alias
            foreach (PropertyInfo property in properties)
            {
                //Get class property value
                string propValue = property.GetValue(p, null).ToString();

                //does our question alias match the property?
                if (propValue == question.QuestionAlias)
                {
                    //Add the value to our namevaluecollection
                    AuthorizeNetPost.AuthNetFormValues.Add(propValue, response);

                    //Response.Write("Found match! " + propValue);
                }

                //Response.Write(string.Format("{0}, {1}", property.Name, property.GetValue(p, null)) + "<br />");
            }
        }

        private void BuildQuantities(WebFormQuestion question, string response)
        {
            try // to extract quantities
            {

                int qty = 0;

                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(_quantityAlias, ""), qty);

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

        private Decimal BuildTotals()
        {

            ////  Debugging Code used to see where there are failures in the sorted dictionaries (containing products/prices)

            //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;

                //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());
            }

            return totalAmount;
        }

        private void BuildEmail(StringBuilder emailText, string subject, Decimal totalAmount)
        {
            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;

                subject = _prependSubject + subject;
                string msg = string.Empty;

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

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

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

        /// <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

    }
}
Comments are closed on this post.