How to

Sana Commerce 8.3
Your provider

Create a Custom External Payment Module for a PSP

Besides internal payment modules there is also means in the framework to easy implement any payment service provider. By default the Sana Commerce framework supports a few PSP's already. These payment modules are distributed as separate modules. If a payment service provider module does not exist yet it can also be added as a custom library. This can be done the following way:

Step 1: First study the documentation of the PSP to see what has to be customized in the default external payment module (for example, custom steps).

Step 2: Create a new assembly/project that will contain the custom payment method, while it is possible to add it to any existing assembly, this will make it easier to distribute the payment method.

Step 3: Create a new payment class that will inherit from the 'ExternalPaymentModule' Class, this will be the base class for implementing the custom PSP provider.

Functions Found in the External Payment Module Base Class 
The 'ExternalPaymentModule' is an important part in creating a PSP implementation. It contains a basic 'framework' for implementation. It contains the following function that can be overridden:
 
Pay 
Signature 
protected override bool OnPay(IOrderContext order, NameValueCollection externalConfiguration)
Description The 'Pay' function is called whenever a payment is started. This function will call the pre-process pipeline to prepare the redirection to the payment service provider.
When to overwrite Customization of this function is uncommon. Configuration independent from the configuration of the payment module can be added with the 'externalConfiguration' parameter. These values will be directly added to the configuration.
 
Confirm 
Signature
public virtual void Confirm(IOrder order, HttpRequest request, NameValueCollection externalParameters)
Description The 'Confirm' function will execute the confirm pipeline and is called by the confirm page.
When to overwrite It functions the same way as the 'Pay' function and it is also unlikely it will need to be overwritten.
 
Finish 
Signature 
public virtual void Finish(IOrder order, HttpRequest request, NameValueCollection externalParameters)
Description This function is the same as the 'Pay' and 'Confirm' functions only that the post-process pipeline is executed.
When to overwrite    -
 
Aborted
Signature
public virtual void Aborted(IOrder order, HttpRequest request, NameValueCollection externalParameters)
Description Again the same as 'Pay', 'Confirm' and 'Finish' but this time the Cancel pipeline is called.
When to overwrite  -
 
CreateResponse
Signature
public virtual NameValueCollection CreateResponse(IOrder order, NameValueCollection externalParameters)
Description 
 This function is called to redirect the user to the PSP payment page. This can be done in three ways: 
  •  A simple redirect;
  • A form POST;
  • A form POST as XML.
The method chosen will depend on the configuration of the payment module.  When post is selected all configuration values marked as 'isRequestParameter' will be added to the post.
When to overwrite    While the system is flexible in some cases another means of redirection might be needed. In that case this function can be overwritten.
 
Initialize
Signature
protected virtual PaymentContext Initialize(IOrderContext order, NameValueCollection externalParameters)
Description This function will load all configuration and pipelines and put them into the 'PaymentContext' object. The 'Payment Context' will be passed to each pipeline.
The 'SetPaymentConfiguration' and 'BuildPipelines' functions are called by this function.
When to overwrite     When trying to overwrite pipeline behaviour it is advised to overwrite the 'BuildPipeline' functions instead. When trying to change the way configuration is loaded (for example from another location) the 'ConfigurationPaymentModuleCreator' should be overwritten. In all other cases this method can be customized.
 
SetPaymentConfiguration
Signature 
protected virtual void SetPaymentConfiguration(IOrder order, List<PaymentConfigurationSetting> configuration)
Description  This function is called each time the configuration is initialized.
When to overwrite When making runtime changes to configuration values (for example replacing values) this function can be overwritten. By default this function does not contain any code. Again when making changes to the way configuration is fetched overwrite the 'ConfigurationPaymentModuleCreator' instead. Note that the configuration is loaded only once for each request but this function is called every request.
 
GetPaymentStatus
Signature
public virtual string GetPaymentStatus(IOrderContext context)
Description  Maps the payment service provider status to a matching Sana commerce status. By default the 'PaymentStatusLookup' table is used for this and status mapping can be changed here.
When to overwrite If the way the status is mapped needs to be changed it can be changed by overwriting this function. This function will generate a table of status 'translations' and pass it to the 'ResolvePaymentStatus' method.
 
ResolvePaymentStatus
Signature 
protected virtual string ResolvePaymentStatus(string status, string method, IEnumerable<IPaymentStatusLookup> statusLookup)
Description  Will set the correct Sana commerce status based on the 'translations' table generated by the 'GetPaymentStatus' function.
When to overwrite This function can be changed if there are exceptions in how certain statuses should be handled that cannot be handled by just changing the translations table.
 
 GetPSPPaymentStatus
Signature 
protected virtual string GetPSPPaymentStatus(IOrderContext context)
Description Function to get the PSP status from an order, by default it will take the 'status' field from the order. The status of an order should be placed in this field.
When to overwrite When this logic needs to be changed.
 
GetPSPPaymentMethod
Signature 
protected virtual string GetPSPPaymentMethod(IOrderContext context)
Description Like the 'GetPSPPaymentStatus' function this is used to get the payment method as defined by the PSP from the order the same way. Only this time it is stored in the 'method' field.
When to overwrite When this logic needs to be changed.
 
GetPSPPaymentInformation
Signature
protected virtual string GetPaymentInformation(IOrder order, string key)
Description Internal function to get information from the field values of an order in a safe way.
When to overwrite In most cases this function will remain the same.
 
BuildPipelines
Signature 
protected virtual void BuildPipelines()
Description Base function that will construct all pipelines used by this payment module. It will make calls to the 'BuildCustomPipelines', 'BuildPreProcessPipeline', 'BuildConfirmPipeline', 'BuildPostProcessPipeline' and 'BuildCancelPipeline' functions.
When to overwrite It is advised to only change these sub-function instead of the 'BuildPipelines' function in order to keep the existing steps in unmodified pipelines. The only exception is when every pipeline is changed and new pipelines are also added.
 
BuildPreProcessPipelines
Signature 
protected virtual IPaymentPipeline BuildPreProcessPipeline()
Description  Sets up the sets that are done by the 'Pre-Process' pipeline.
When to overwrite If any steps need to be changed, removed or added this method should be overwritten to return the new pipeline.
 
Note that the 'PaymentInformation' property of the 'OrderContext' has to be set in the first step of PreProcessPipeline. Below you can find an example of a complete payment step:
using System;
using System.Collections.Generic;
using Sana.Commerce.Payment.Pipeline;
namespace Sana.Commerce.Payment.DocData
{
    /// <summary>
    /// This step is responsible for initializing Payment Information.
    /// </summary>
    public class InitializePaymentInformationStep : IPaymentStep
    {
        /// <summary>
        /// Initialize Request information in the Order Context.
        /// </summary>
        /// <param name="entity">The strongly typed objects that should be passed to the step.</param>
        /// <param name="properties">A collection of objects that can be extended in any way.</param>
        public void Execute(IOrderContext entity, IDictionary<string, object> properties)
        {
            //Add Payment Request information to the Order
            entity.Order[Order.OrderFields.PaymentInformation] = CreatePaymentRequestValues(entity);
        }
        /// <summary>
        /// Creates the payment request values.
        /// </summary>
        /// <param name="entity">Order context.</param>
        /// <returns>Payment information.</returns>
        protected virtual Order.PaymentInformation CreatePaymentRequestValues(IOrderContext entity)
        {
            var configValues = new Order.PaymentInformation();
            string paymentInfoField = Order.OrderFields.PaymentInformation;
            if (entity.Order.Fields.ContainsKey(paymentInfoField))
                configValues = (Order.PaymentInformation)entity.Order[paymentInfoField];            
            foreach (var config in entity.Payment.Configuration)
            {
                if (config.IsRequestParameter)
                    configValues.Request.Add(new Order.PaymentConfiguration { Name = config.Name, Value = config.Value });
            }            
            return configValues;
        }
    }
}
BuildConfirmPipelines
Signature 
protected virtual IPaymentPipeline BuildConfirmPipeline()
Description Sets up the sets that are done by the 'Confirm' pipeline.
When to overwrite If any steps need to be changed, removed or added this method should be overwritten to return the new pipeline.
 
BuildPostProcessPipelines
Signature
protected virtual IPaymentPipeline BuildPostProcessPipeline()
Description Sets up the sets that are done by the 'Post-Process' pipeline.
When to overwrite If any steps need to be changed, removed or added this method should be overwritten to return the new pipeline.
 
BuildCancelPipelines
Signature
protected virtual IPaymentPipeline BuildCancelPipeline()
Description Sets up the sets that are done by the 'Cancel' pipeline.
When to overwrite If any steps need to be changed, removed or added this method should be overwritten to return the new pipeline.
 
BuildCustomPipelines
Signature
protected virtual List<IPaymentPipeline> BuildCustomPipelines()
Description This function will create all child pipelines. These pipelines can be used in steps in the four main pipelines. In the default implementation they are used as pipelines that are executing depending on the status of the order in the confirm pipeline.
When to overwrite If any of these steps need to be changed or if another child pipeline has to be created, this method needs to be overwritten.
 
Note when overwriting the 'BuildCustomPipelines' method be sure to construct all needed pipelines, this also means all pipelines that are now constructed by default. The following code can be copied when customizing:
 
List<IPaymentPipeline> pipeline = new List<IPaymentPipeline>();
 //Pipeline executed if paymentstatus is ok
 var okSteps = new IPaymentStep[ ]
 {    
  //Add steps here
  new PayOrderStep(),     //Set order status to paid
  new DeductStockStep(),  //Deduct the stock from the ordered products
  new SendOrderMailStep(),    //Send an order mail to the user
  new DeleteBasketStep()     //Delete the basket of the user
 };
 pipeline.Add(new PaymentPipeline("StatusOkPipeline", okSteps));
 var cancelSteps = new IPaymentStep[ ]
 {    
  //Add steps here
   new CancelOrderStep()
 };
 pipeline.Add(new PaymentPipeline("StatusCancelPipeline", cancelSteps));
 var pendingSteps = new IPaymentStep[ ]
 {    
   //Add steps here         
   new PendingOrderStep(),
   new SendOrderMailStep()  
 };
 pipeline.Add(new PaymentPipeline("StatusPendingPipeline",
 pendingSteps));
 return pipeline;
 
 
GetXmlResponseParameter
Signature
protected virtual NameValueCollection GetXmlReponseParameter(IOrder order)
Description This function will be called when the 'PostUsesXML' setting is set to 'true'. It will generate the XML that will be posted to the PSP.
When to overwrite To change the format of the XML this method should be overwritten. It should return a 'NameValueCollection' with a single value containing the xml field.
 
GetResponseParameter
Signature
protected virtual NameValueCollection GetReponseParameters(IOrder order)
Description Creates a 'NameValueCollection' of all values that have the 'isRequestParameter' set to true.
When to overwrite Normally this function does not have to be customized.
 
GenerateRedirectUrl
Signature
protected virtual string GenerateRedirectUrl(IOrder order, bool getParameters)
Description This function generates the URL that the user will be directed to at the PSP site.
When to overwrite This can normally be changed by changing the 'PaymentPage Configuration' value and should not have to be customized.
 
Custom Configuration Values 
To add custom configuration values to the PSP the 'isRequestParameter' property can be used. Besides the name and value field, each configuration value also has this property. If set to true, the value will be directly sent to the PSP in the request (in hidden form our URL). This way it is easy to add new parameters to the PSP without changing the payment module code. But be sure to only set this parameter to true, if it really needs to be passed to the PSP to prevent cluttering of data.
 
If set to false the configuration value must explicitly be used in the code of the payment module or in one of the steps executed in the module. In this case you will need to write custom code to handle the configuration value. This is easily done the following way:
 
The value of the configuration will be automatically loaded (once the application starts) in the Configuration property of the payment module (as defined in 'IPaymentModule').
 
For example, we add a new configuration setting to a custom module:
<add name="CustomValue" value="1" isRequestParameter="false" /> 
In the code of the custom module we can then get the value of this configuration setting with the following line:
Configuration.Where(value => value.Name = "CustomValue").First().Value;
In external payment pipelines the configuration will be passed into the context object and can then be retrieved in the same way. After that the effect of the new configuration setting can then be defined by the implemented custom code.