How to integrate First Data Gateway e4 with .NET WCF?

Recently I worked on a project that needed integration with First Data Getaway e4 payment processing. Starting from v12 First Data introduced HMAC hash security. I found .NET REST sample, but there is nothing for WCF or Web Services. This new HMAC security feature makes usage of Web Service a little challenging. I hope this guide will help somebody.

Step 1. Add service reference:


WCF: Add Service Reference

WCF: Add Service Reference Step 2

Step 2. Create demo account

Step 3. Use following code wrapper to charge a card:

public class Merchant
{
    private readonly string _gatewayId;
    private readonly string _password;
    private readonly string _keyId;
    private readonly string _hmac;
    private readonly bool _isDemo;

    private const string ProdUrl = "https://api.globalgatewaye4.firstdata.com/transaction/v12";
    private const string TestUrl = "https://api.demo.globalgatewaye4.firstdata.com/transaction/v12";


    public Merchant(string gatewayId, string password, string hmac, string keyId, bool isDemo = true)
    {
        _gatewayId = gatewayId;
        _password = password;
        _hmac = hmac;
        _keyId = keyId;
        _isDemo = isDemo;
    }

    public MerchantResponse Charge(int orderId, string cardHoldersName, string cardNumber, decimal amount, 
        int expirationMonth, int expirationYear, int ccv, string address, string city, string state, string zip)
    {            
        var client = new ServiceSoapClient(new BasicHttpBinding(BasicHttpSecurityMode.Transport), 
            new EndpointAddress(_isDemo ? TestUrl : ProdUrl));                                           
        client.ChannelFactory.Endpoint.Behaviors.Add(new HmacHeaderBehaivour(_hmac,_keyId));            

        TransactionResult result = client.SendAndCommit(new Transaction
                                                {                                                                        
                                                    ExactID = _gatewayId,
                                                    Password = _password,
                                                    Transaction_Type = "00",                                                                        
                                                    Card_Number = cardNumber,
                                                    CardHoldersName = cardHoldersName,                                                                                                                                                
                                                    DollarAmount = amount.ToString("F"),
                                                    Expiry_Date = string.Format("{0:D2}{1}",expirationMonth,expirationYear),
                                                    Customer_Ref = orderId.ToString(),
                                                    VerificationStr1 = string.Format("{0}|{1}|{2}|{3}|US",address,zip,city,state),
                                                    VerificationStr2 = ccv.ToString()
                                                });
        var response = new MerchantResponse
                        {
                            IsTransactionApproved = result.Transaction_Approved,
                            IsError = result.Transaction_Error
                        };
        if (!result.Transaction_Approved && !result.Transaction_Error)
        {
            response.Message = string.Format("Error {0}: {1}", result.Bank_Resp_Code, result.Bank_Message);
        }
        if (!result.Transaction_Approved && result.Transaction_Error)
        {
            response.Message = string.Format("Error {0}: {1}",result.EXact_Resp_Code,result.EXact_Message);
        }
        if (result.Transaction_Approved)
        {
            response.Message = result.Authorization_Num;
        }
        return response;
    }               

    class HmacHeaderBehaivour: IEndpointBehavior
    {
        private readonly string _hmac;
        private readonly string _keyId;

        public HmacHeaderBehaivour(string hmac, string keyId)
        {
            _hmac = hmac;
            _keyId = keyId;
        }

        public void Validate(ServiceEndpoint endpoint)
        {                
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {                
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {                
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new HmacHeaderInspector(_hmac,_keyId));                                
        }
    }      
         

    class HmacHeaderInspector: IClientMessageInspector
    {
        private readonly string _hmac;
        private readonly string _keyId;

        public HmacHeaderInspector(string hmac,string keyId)
        {
            _hmac = hmac;
            _keyId = keyId;
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {                                                                
            MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
            request = buffer.CreateMessage();
            Message msg = buffer.CreateMessage();                
            ASCIIEncoding encoder = new ASCIIEncoding();
                
            var sb = new StringBuilder();
            var xmlWriter = XmlWriter.Create(sb, new XmlWriterSettings
                                                    {
                                                    OmitXmlDeclaration  = true
                                                    });
            var writer = XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter);
            msg.WriteStartEnvelope(writer);
            msg.WriteStartBody(writer);
            msg.WriteBodyContents(writer);                                
            xmlWriter.WriteEndElement();
            xmlWriter.WriteEndElement();
            writer.Flush();

            string body = sb.ToString().Replace(" />","/>");

            byte[] xmlByte = encoder.GetBytes(body);
            SHA1CryptoServiceProvider sha1Crypto = new SHA1CryptoServiceProvider();
            string hash = BitConverter.ToString(sha1Crypto.ComputeHash(xmlByte)).Replace("-", "");
            string hashedContent = hash.ToLower();

            //assign values to hashing and header variables
            string time = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
            string hashData = "POST\ntext/xml; charset=utf-8\n" + hashedContent + "\n" + time + "\n/transaction/v12";
            //hmac sha1 hash with key + hash_data
            HMAC hmacSha1 = new HMACSHA1(Encoding.UTF8.GetBytes(_hmac)); //key
            byte[] hmacData = hmacSha1.ComputeHash(Encoding.UTF8.GetBytes(hashData)); //data
            //base64 encode on hmac_data
            string base64Hash = Convert.ToBase64String(hmacData);

            HttpRequestMessageProperty httpRequestMessage;
            object httpRequestMessageObject;

            if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
            {
                httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
                httpRequestMessage.Headers["X-GGe4-Content-SHA1"] = hashedContent;
                httpRequestMessage.Headers["X-GGe4-Date"] = time;
                httpRequestMessage.Headers["Authorization"] = "GGE4_API " + _keyId + ":" + base64Hash;
            }
            else
            {
                httpRequestMessage = new HttpRequestMessageProperty();
                httpRequestMessage.Headers["X-GGe4-Content-SHA1"] = hashedContent;
                httpRequestMessage.Headers["X-GGe4-Date"] = time;                    
                httpRequestMessage.Headers["Authorization"] = "GGE4_API " + _keyId + ":" + base64Hash;
                request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
            }                
            return null;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {                
        }
    }
}

public class MerchantResponse
{
    public bool IsTransactionApproved { get; set; }
    public bool IsError { get; set; }
    public string Message { get; set; }
}

As you can see "Charge" method is simple, but HMAC requirement makes it challenging. HMAC hash is calculated based on SOAP request body, which is not accessible through ServiceSoapClient. Also we need to modify HTTP request header. Both things can be done using IClientMessageInspector or IClientMessageFormatter. In my implementation I used IClientMessageInspector, since it is a little easier to integrate it with ServiceSoapClient. I didn't find any easy way to get serialized SOAP body of the request, so using Fiddler I came up with following magic code:


var sb = new StringBuilder();
var xmlWriter = XmlWriter.Create(sb, new XmlWriterSettings
                                        {
                                        OmitXmlDeclaration  = true
                                        });
var writer = XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter);
msg.WriteStartEnvelope(writer);
msg.WriteStartBody(writer);
msg.WriteBodyContents(writer);                                
xmlWriter.WriteEndElement();
xmlWriter.WriteEndElement();
writer.Flush();

string body = sb.ToString().Replace(" />","/>");

The code above gives me serialized SOAP envelope without SOAP headers string. This string is used for HMAC calculation and at the end X-GGe4-Content-SHA1, X-GGe4-Date, Authorization are added to HTTP header.

Tips: You might get 401 errors. Use Fiddler to see actual error. Make sure that you saved generated HMAC key (generate button doesn't save it).


Posted on Tuesday, January 07, 2014 by | Comments (4) | Add Comment

Comments

Gravatar

Re: How to integrate First Data Gateway e4 with .NET WCF?

I was beating my head against the wall on this one, no help from FirstData at all. You sir, are the man.

Posted on 2/6/2014 7:53:51 AM by Scott #
Gravatar

It took me a while to figure it out. I am glad that it helps somebody.

Gravatar

Re: How to integrate First Data Gateway e4 with .NET WCF?

This post was a life saver. Thanks so much for sharing!

Posted on 4/9/2014 7:07:03 AM by Joe #
Gravatar

Re: How to integrate First Data Gateway e4 with .NET WCF?

How we can get orderId for making charge transaction?

Posted on 5/20/2014 4:51:41 AM by Hayk #
Gravatar

It should be you internal orderId. Internally you should keep track of all orders and by orderId you can pull up specific order details.

Gravatar

Re: How to integrate First Data Gateway e4 with .NET WCF?

If you're interested in making a some money and helping to implement this into our current site, please email me. I've been working on this and smashing my head also. Even tried hiring a freelancer and he's just been more frustrating. My client is really hoping to get this done within the next week. If you're interested please let me know ASAP.

Posted on 6/16/2014 1:41:39 PM by Bryan #

New Comment

Your Name:
Email (for internal use only):
Comment:
 
Code above:

Categories

Recent Tweets

  • Hierarchies with HierarchyID in SQL 2008http://blogs.msdn.com/b/simonince/archive/2008/10/17/hierarchies-with-hierarchyid-in-sql-2008.aspx
  • Visual Studio 2010 WAS painfully slow - CodeProject http://t.co/Usba1x6CZy
  • VisualSVN Subversion Server and Git | SubGit Blog http://t.co/S2FwsNKD2m

Valid HTML5