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