Integrating A Payment Gateway

From AbleCommerce Wiki
Jump to: navigation, search

IPaymentProvider

To integrate a payment gateway with Ablecommerce 7 you need to implement CommerceBuilder.Payments.Providers.IPaymentProvider interface. This interface defines how payment providers interact with Ablecommerce.

In version 7.0 final, IPaymentProvider interface is defined as follows

    /// <summary>
    /// Interface that all payment providers implementations must implement
    /// </summary>
    public interface IPaymentProvider
    {
        /// <summary>
        /// Id of the payment gateway in database. Id is passed at the time of initialization.
        /// </summary>
        int PaymentGatewayId { get; }

        /// <summary>
        /// Name of the payment provider implementation
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Description of the payment provider implementation
        /// </summary>
        string Description { get; }

        /// <summary>
        /// Version of the payment provider implementation
        /// </summary>
        string Version { get; }

        /// <summary>
        /// Whether debug mode is enabled or not
        /// </summary>
        bool UseDebugMode { get; set; }

        /// <summary>
        /// The transactions that are supported by the payment provider implementation
        /// </summary>
        SupportedTransactions SupportedTransactions { get; }

        /// <summary>
        /// Initializes the payment provider implementation. Called by AC at the time of initialization.
        /// </summary>
        /// <param name="PaymentGatewayId">Id of the payment gateway in database</param>
        /// <param name="ConfigurationData">Configuration data as name-value pairs</param>
        void Initialize(int PaymentGatewayId, Dictionary<String, String> ConfigurationData);

        /// <summary>
        /// Builds an input form for getting configuration data. Input form is built inside the given
        /// ASP.NET parent control.
        /// </summary>
        /// <param name="parentControl">ASP.NET control to which to add the input form</param>
        void BuildConfigForm(System.Web.UI.Control parentControl);

        /// <summary>
        /// Gets a reference string to identify a particular configuration.
        /// </summary>
        string ConfigReference { get; }

        /// <summary>
        /// Gets a URL for the logo of the payment provider implementation
        /// </summary>
        /// <param name="cs">ClientScriptManager</param>
        /// <returns>A Url for the logo of the payment provider implementation</returns>
        string GetLogoUrl(System.Web.UI.ClientScriptManager cs);

        /// <summary>
        /// Submits an authorization request
        /// </summary>
        /// <param name="authorizeRequest">The authorization request</param>
        /// <returns>Transaction that represents the result of the authorization request</returns>
        Transaction DoAuthorize(AuthorizeTransactionRequest authorizeRequest);

        /// <summary>
        /// Submits a capture request for a previously authorized transaction
        /// </summary>
        /// <param name="captureRequest">The capture request</param>
        /// <returns>Transaction that represents the result of the capture request</returns>
        Transaction DoCapture(CaptureTransactionRequest captureRequest);

        /// <summary>
        /// Submits a refund request for a previously captured transaction
        /// </summary>
        /// <param name="creditRequest">The refund request</param>
        /// <returns>Transaction that represents the result of the refund request</returns>
        Transaction DoRefund(RefundTransactionRequest creditRequest);

        /// <summary>
        /// Submits a void request for a previously authorized transaction
        /// </summary>
        /// <param name="voidRequest">The void request</param>
        /// <returns>Transaction that represents the result of the void request</returns>
        Transaction DoVoid(VoidTransactionRequest voidRequest);

        /// <summary>
        /// Submits a recurring billing authorization request.
        /// </summary>
        /// <param name="authorizeRequest">An authorize request that contains the details of the order and payment.</param>
        /// <returns>A transaction instance that contains the results of the recurring billing request</returns>
        AuthorizeRecurringTransactionResponse DoAuthorizeRecurring(AuthorizeRecurringTransactionRequest authorizeRequest);

        /// <summary>
        /// Requests cancellation of a recurring billing authorization.
        /// </summary>
        /// <param name="cancelRequest">A cancel request that contains the details of the recurring payment.</param>
        /// <returns>A transaction instance that cnotains the results of the cancel recurring billing request.</returns>
        Transaction CancelAuthorizeRecurring(CancelRecurringTransactionRequest cancelRequest);
    }


PaymentProviderBase

You can either implement the IPaymentProvider interface directly or you can extend the CommerceBuilder.Payments.Providers.PaymentProviderBase class. For the sake of easy upgrades it is highly recommended that you extend PaymentProviderBase class instead of directly implementing the IPaymentProvider interface. The PaymentProviderBase class abstracts out the repetitive tasks and provides you some additional utility functions.

The PaymentProviderBase class is defined as follows

    /// <summary>
    /// A base class that can be extended by payment provider implementations
    /// </summary>
    public abstract class PaymentProviderBase : IPaymentProvider
    {

        #region IPaymentProvider Members

        private int _PaymentGatewayId;
        public int PaymentGatewayId { get { return _PaymentGatewayId;}}
                
        public abstract string Name { get; }
        public abstract string Description { get; }
        public abstract string Version { get; }

        bool _UseDebugMode;
        public bool UseDebugMode
        {
            get { return _UseDebugMode; }
            set { _UseDebugMode = value; }
        }

        public abstract SupportedTransactions SupportedTransactions { get; }

        public virtual void Initialize(int PaymentGatewayId, Dictionary<String, String> ConfigurationData)
        {
            this._PaymentGatewayId = PaymentGatewayId;
            if (ConfigurationData.ContainsKey("UseDebugMode")) UseDebugMode = bool.Parse(ConfigurationData["UseDebugMode"]);
        }

        public abstract void BuildConfigForm(System.Web.UI.Control parentControl);

        public abstract string ConfigReference { get; }

        public abstract string GetLogoUrl(System.Web.UI.ClientScriptManager cs);

        public abstract Transaction DoAuthorize(AuthorizeTransactionRequest authorizeRequest);

        public abstract Transaction DoCapture(CaptureTransactionRequest captureRequest);

        public abstract Transaction DoRefund(RefundTransactionRequest creditRequest);

        public abstract Transaction DoVoid(VoidTransactionRequest voidRequest);

        public virtual AuthorizeRecurringTransactionResponse DoAuthorizeRecurring(AuthorizeRecurringTransactionRequest authorizeRequest)
        {
            Transaction transaction = new Transaction();
            transaction.PaymentGatewayId = this.PaymentGatewayId;
            transaction.TransactionType = authorizeRequest.TransactionType;
            transaction.TransactionStatus = TransactionStatus.Failed;
            transaction.ResponseCode = "U";
            transaction.ResponseMessage = "Recurring billing is unsupported by this gateway integration.";
            transaction.RemoteIP = authorizeRequest.RemoteIP;
            AuthorizeRecurringTransactionResponse response = new AuthorizeRecurringTransactionResponse();
            response.Status = transaction.TransactionStatus;
            response.AddTransaction(transaction);
            return response;
        }

        public virtual Transaction CancelAuthorizeRecurring(CancelRecurringTransactionRequest cancelRequest)
        {
            Transaction transaction = new Transaction();
            transaction.PaymentGatewayId = this.PaymentGatewayId;
            transaction.TransactionType = cancelRequest.TransactionType;
            transaction.TransactionStatus = TransactionStatus.Failed;
            transaction.ResponseCode = "U";
            transaction.ResponseMessage = "Cancellation of recurring billing is unsupported by this gateway integration.";
            transaction.RemoteIP = cancelRequest.RemoteIP;
            return transaction;
        }

        #endregion

        /// <summary>
        /// Records a transaction message to the debug log.
        /// </summary>
        /// <param name="providerName">Name of the provider or gateway</param>
        /// <param name="direction">Indicates whether the data was sent or received</param>
        /// <param name="message">Content of the message</param>
        /// <param name="sensitiveData">A dictionary of key/value pairs that contains sensitive 
        /// data that exists within the message (key) and the desired replacement (value).  
        /// Pass null if no replacements are required.</param>
        protected void RecordCommunication(string providerName, CommunicationDirection direction, string message, Dictionary<string,string> sensitiveData)
        {
            //CHECK FOR SENSITIVE DATA THAT MUST BE SCRUBBED
            if (sensitiveData != null)
            {
                foreach (string key in sensitiveData.Keys)
                {
                    message = message.Replace(key, sensitiveData[key]);
                }
            }
            //GET LOG DIRECTORY
            string directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data\\Logs\\");
            if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
            string fileName = Path.Combine(directory, providerName + ".Log");
            using (StreamWriter sw = File.AppendText(fileName))
            {
                sw.WriteLine(direction.ToString() + ": " + message);
                sw.WriteLine(string.Empty);
                sw.Close();
            }
        }

        /// <summary>
        /// Creates a reference number from the given credit card account number. 
        /// Leaves the last 4 characters in place and replaces the rest with x
        /// </summary>
        /// <param name="accountNumber">The account number to make reference for</param>
        /// <returns>The reference number</returns>
        protected string MakeReferenceNumber(string accountNumber)
        {
            if (string.IsNullOrEmpty(accountNumber)) return string.Empty;
            int length = accountNumber.Length;
            if (length < 5)
            {
                return new string('x', length);
            }
            return new string('x', length - 4) + accountNumber.Substring(length - 4);
        }

        /// <summary>
        /// Enumeration that represents the direction of communication between AC and the gateway
        /// </summary>
        public enum CommunicationDirection : int { 
            /// <summary>
            /// Sending data to the gateway
            /// </summary>
            Send,
 
            /// <summary>
            /// Receiving data from the gateway
            /// </summary>
            Receive 
        };

    }

Implementing Required Methods

When you extend PaymentProviderBase class you need to implement the abstract methods and properties defined in this class. Some of the properties and methods are for information purposes only and are easy to implement. For example Name, Description, and Version. Here is an example of how these methods are implemented in Authorize.NET provider.

        public override string Name
        {
            get { return "Authorize.Net"; }
        }

        public override string Description
        {
            get { return "Authorize.Net is the preferred payment gateway among resellers and merchants for managing payment transactions using the power and speed of the Internet. Authorize.Net also provides other business enhancing products that help merchants manage their transactions. For more information about Authorize.Net, see the <a href=\"http://www.authorizenet.com/company/aboutus\" target=\"_blank\">About Us</a> page."; }
        }

        public override string Version
        {
            get { return "AIM 3.1"; }
        }

SupportedTransactions property

The SupportedTransactions property lets Ablecommerce know which transactions are supported by this payment gateway implementation. Based on the transactions supported by the payment provider Ablecommerce makes appropriate calls to the provider. Here is how SupportedTransactions property is implemented in the Authorize.NET implementation.

        public override SupportedTransactions SupportedTransactions
        {
            get
            {
                return (SupportedTransactions.Authorize 
                        | SupportedTransactions.AuthorizeCapture 
                        | SupportedTransactions.Capture 
                        | SupportedTransactions.PartialRefund 
                        | SupportedTransactions.Refund 
                        | SupportedTransactions.Void 
                        | SupportedTransactions.RecurringBilling);
            }
        }

For more information on transaction types see Payment Gateway Transaction Types

DoXXX Methods

The most important methods where the actual work gets done are DoXXX methods. These are the methods where the implementations connect to the provider's servers and execute the required transactions. Implementation of these methods varies for each provider. Some providers may use XML based APIs while others may use HTML Form based APIs. Depending on how your provider likes to be talked to you will implement these methods accordingly. To see how these methods are implemented for Authorize.NET implementation download the complete source of Authorize.NET implementation here. TODO : Provide Authorize.NET provider download

Initialize and BuildConfigForm

The Initialize method is called by Ablecommerce at the time of creation of the payment provider object. The configuration parameters loaded from the database are passed to this method. The implementation classes should read their custom configuration parameters and initialize themselves in this method accordingly. Here is how Initialize is implemented in Authorize.NET implementation.

        public override void Initialize(int PaymentGatewayId, Dictionary<string, string> ConfigurationData)
        {
            base.Initialize(PaymentGatewayId, ConfigurationData);
            if (ConfigurationData.ContainsKey("MerchantLogin")) MerchantLogin = ConfigurationData["MerchantLogin"];
            if (ConfigurationData.ContainsKey("TransactionKey")) TransactionKey = ConfigurationData["TransactionKey"];
            if (ConfigurationData.ContainsKey("UseAuthCapture")) UseAuthCapture = AlwaysConvert.ToBool(ConfigurationData["UseAuthCapture"], true);
            if (ConfigurationData.ContainsKey("GatewayMode")) GatewayMode = (GatewayModeOption)AlwaysConvert.ToInt(ConfigurationData["GatewayMode"]);
            if (ConfigurationData.ContainsKey("IsSecureSource")) IsSecureSource = (ConfigurationData["IsSecureSource"] == "on");
        }

An important part of getting a payment provider implemented is to get it integrated in the merchant admin console. From the merchant admin, you will add or edit the gateway configuration settings for your implementation. BuildConfigForm creates the web form that is used for this purpose. In this method you create an appropriate form inside the given ASP.NET control. An example of how the Authorize.NET implementation in AC7 creates its configuration form is given below.

        public override void BuildConfigForm(Control parentControl)
        {
            //CREATE CONFIG TABLE
            HtmlTable configTable = new HtmlTable();
            configTable.CellPadding = 4;
            configTable.CellSpacing = 0;
            HtmlTableRow currentRow;
            HtmlTableCell currentCell;
            configTable.Attributes.Add("class", "inputForm");
            configTable.Attributes.Add("style", "border:none");

            //ADD CAPTION
            currentRow = new HtmlTableRow();
            currentCell = new HtmlTableCell("th");
            currentCell.Attributes.Add("class", "sectionHeader");
            currentCell.ColSpan = 2;
            HyperLink gatewayLink = new HyperLink();
            gatewayLink.Text = this.Name;
            gatewayLink.NavigateUrl = "http://www.authorizenet.com";
            gatewayLink.Target = "_blank";
            currentCell.Controls.Add(gatewayLink);
            currentRow.Cells.Add(currentCell);
            configTable.Rows.Add(currentRow);

            //ADD INSTRUCTION TEXT
            currentRow = new HtmlTableRow();
            currentCell = new HtmlTableCell();
            currentCell.ColSpan = 2;
            currentCell.Controls.Add(new LiteralControl("<p class=\"InstructionText\">To enable Authorize.Net, you must provide your merchant login and transaction key.  If you do not already have a transaction key, it can be generated from the Authorize.Net merchant interface.</p>"));
            currentRow.Cells.Add(currentCell);
            configTable.Rows.Add(currentRow);
         
            //GET THE LOGIN (#0) AND SECURESOURCE (#6)
            currentRow = new HtmlTableRow();
            currentCell = new HtmlTableCell("th");
            currentCell.Attributes.Add("class", "rowHeader");
            currentCell.Attributes.Add("style", "white-space:normal;");
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            currentCell.Controls.Add(new LiteralControl("Merchant Login:"));
            currentCell.Controls.Add(new LiteralControl("<br /><span class=\"helpText\">Your Authorize.Net merchant login is required.</span>"));
            currentRow.Cells.Add(currentCell);
            currentCell = new HtmlTableCell();
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            TextBox txtMerchantLogin = new TextBox();
            txtMerchantLogin.ID = "Config_MerchantLogin";
            txtMerchantLogin.Columns = 20;
            txtMerchantLogin.MaxLength = 50;
            txtMerchantLogin.Text = this.MerchantLogin;
            currentCell.Controls.Add(txtMerchantLogin);
            currentCell.Controls.Add(new LiteralControl("<br />"));
            CheckBox chkIsSecureSource = new CheckBox();
            chkIsSecureSource.ID = "Config_IsSecureSource";
            chkIsSecureSource.Checked = this.IsSecureSource;
            currentCell.Controls.Add(chkIsSecureSource);
            currentCell.Controls.Add(new LiteralControl(" Check here if you are a Wells Fargo SecureSource merchant."));
            currentRow.Cells.Add(currentCell);
            configTable.Rows.Add(currentRow);

            //GET THE TRANSACTION KEY
            currentRow = new HtmlTableRow();
            currentCell = new HtmlTableCell("th");
            currentCell.Attributes.Add("class", "rowHeader");
            currentCell.Attributes.Add("style", "white-space:normal;");
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            currentCell.Controls.Add(new LiteralControl("Transaction Key:"));
            currentCell.Controls.Add(new LiteralControl("<br /><span class=\"helpText\">The transaction key for your merchant account is required.</span>"));
            currentRow.Cells.Add(currentCell);
            currentCell = new HtmlTableCell();
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            TextBox txtTransactionKey = new TextBox();
            txtTransactionKey.ID = "Config_TransactionKey";
            txtTransactionKey.Columns = 20;
            txtTransactionKey.MaxLength = 50;
            txtTransactionKey.Text = this.TransactionKey;
            currentCell.Controls.Add(txtTransactionKey);
            currentRow.Cells.Add(currentCell);
            configTable.Rows.Add(currentRow);

            //GET THE AUTHORIZATION MODE
            currentRow = new HtmlTableRow();
            currentCell = new HtmlTableCell("th");
            currentCell.Attributes.Add("class", "rowHeader");
            currentCell.Attributes.Add("style", "white-space:normal;");
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            currentCell.Controls.Add(new LiteralControl("Authorization Mode:"));
            currentCell.Controls.Add(new LiteralControl("<br /><span class=\"helpText\">Use \"Authorize\" to request authorization without capturing funds at the time of purchase. You can capture authorized transactions through the order admin interface. Use \"Authorize & Capture\" to capture funds immediately at the time of purchase.</span>"));
            currentRow.Cells.Add(currentCell);
            currentCell = new HtmlTableCell();
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            RadioButtonList rblTransactionType = new RadioButtonList();
            rblTransactionType.ID = "Config_UseAuthCapture";
            rblTransactionType.Items.Add(new ListItem("Authorize (recommended)", "false"));
            rblTransactionType.Items.Add(new ListItem("Authorize & Capture", "true"));
            rblTransactionType.Items[UseAuthCapture ? 1 : 0].Selected = true;
            currentCell.Controls.Add(rblTransactionType);
            currentRow.Cells.Add(currentCell);
            configTable.Rows.Add(currentRow);

            //GET THE GATEWAY MODE
            currentRow = new HtmlTableRow();
            currentCell = new HtmlTableCell("th");
            currentCell.Attributes.Add("class", "rowHeader");
            currentCell.Attributes.Add("style", "white-space:normal;");
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            currentCell.Controls.Add(new LiteralControl("Gateway Mode:"));
            currentCell.Controls.Add(new LiteralControl("<br /><span class=\"helpText\">You can configure AbleCommerce to use either the production or test gateway with live or test transactions. Enabling test mode in the Authorize.Net merchant interface will override the live mode option set here.</span>"));
            currentRow.Cells.Add(currentCell);
            currentCell = new HtmlTableCell();
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            RadioButtonList rblGatewayMode = new RadioButtonList();
            rblGatewayMode.ID = "Config_GatewayMode";
            rblGatewayMode.Items.Add(new ListItem("Production Gateway, Live Mode", ((int)GatewayModeOption.ProductionServerLiveMode).ToString()));
            rblGatewayMode.Items.Add(new ListItem("Production Gateway, Test Mode", ((int)GatewayModeOption.ProductionServerTestMode).ToString()));
            rblGatewayMode.Items.Add(new ListItem("Test Gateway, Live Mode", ((int)GatewayModeOption.TestServerLiveMode).ToString()));
            rblGatewayMode.Items.Add(new ListItem("Test Gateway, Test Mode", ((int)GatewayModeOption.TestServerTestMode).ToString()));
            rblGatewayMode.Items[(int)this.GatewayMode].Selected = true;
            currentCell.Controls.Add(rblGatewayMode);
            currentRow.Cells.Add(currentCell);
            configTable.Rows.Add(currentRow);

            //GET THE DEBUG MODE
            currentRow = new HtmlTableRow();
            currentCell = new HtmlTableCell("th");
            currentCell.Attributes.Add("class", "rowHeader");
            currentCell.Attributes.Add("style", "white-space:normal;");
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            currentCell.Controls.Add(new LiteralControl("Debug Mode:"));
            currentCell.Controls.Add(new LiteralControl("<br /><span class=\"helpText\">When debug mode is enabled, the communication between AbleCommerce and Authorize.Net is recorded in the store \"logs\" folder. Sensitive information is stripped from the log entries.</span>"));
            currentRow.Cells.Add(currentCell);
            currentCell = new HtmlTableCell();
            currentCell.VAlign = "Top";
            currentCell.Width = "50%";
            RadioButtonList rblDebugMode = new RadioButtonList();
            rblDebugMode.ID = "Config_UseDebugMode";
            rblDebugMode.Items.Add(new ListItem("Off", "false"));
            rblDebugMode.Items.Add(new ListItem("On", "true"));
            rblDebugMode.Items[UseDebugMode ? 1 : 0].Selected = true;
            currentCell.Controls.Add(rblDebugMode);
            currentRow.Cells.Add(currentCell);
            configTable.Rows.Add(currentRow);

            //CREATE LITERAL CONTROL WITH HTML CONTENT
            parentControl.Controls.Add(configTable);
        }

If BuildConfigForm and Initialize methods are implemented correctly, saving and loading of configuraion parametes will be handled transparently by Ablecommerce.

GetLogoUrl

The GetLogoUrl is supposed to return a URL that would give the logo of the provider. This URL can be an external URL or it can be a URL returning the logo from an embedded resource. This is not a very important method as it does not affect the actual functioning of the provider. Here is an example of how this method is implemented for Authorize.NET. The logo in Authorize.NET provider is embedded in the DLL.

        public override string GetLogoUrl(ClientScriptManager cs)
        {
            if (cs != null)
                return cs.GetWebResourceUrl(this.GetType(), "CommerceBuilder.Payments.Providers.AuthorizeNet.Logo.gif");
            return string.Empty;
        }


Deployment

Once you have completed the implementation code you need to compile your classes and put the resulting DLL in Bin folder of your Ablecommerce installation. Once your DLL is placed in the Bin folder, Ablecommerce will automatically detect the new gateway and allow you to add, remove and configure it just like other payment gateways.


Resources