- Posted by miketeye on July 30, 2010
Share on FacebookRecently I was working on a payment integration effort for an e-commerce site using the SagePay (formerly Protx) payment gateway. I had previously done similar a while ago and relied heavily on a then Protx released assembly - Protx.Vsp.dll.
Well it turns out, under its new incarnation as SagePay, they have released a new version of their gateway protocol (2.23). The Protx.Vsp.dll implemented the 2.22 protocol and so has been discontinued so I was really left in the cold. Because the architecture of the .net e-commerce site I was working on used a paymentService architecture which supported multiple payment gateways by implementing provider specific logic in provider implementations, I needed a library like Protx.Vsp.dll to abstract away all the fine workins of the SagePay protocol. So I started work on developing a new version of the Protx.Vsp.dll assembly named SaePay.Vsp.dll. When it is good to release, I might consider releasing it as open source.
However, in the process of building this library, I needed to validate the VPSSignature parameter returned as part of the response from the Server Registration Transaction. I did everything I thought was right as per the Sage Server Integration guide, but failed to match the signature, that is until a EUREKA moment explained below.
The text below is directly from the Sage Server Integration Documentation:
VPSSignature = MD5 signature of the concatenation of the values of: VPSTxId + VendorTxCode + Status + TxAuthNo + VendorName+ AVSCV2 + SecurityKey + AddressResult + PostCodeResult +CV2Result + GiftAid + 3DSecureStatus + CAVV + AddressStatus + PayerStatus + CardType + Last4Digits.
NOTE: MD5 value is returned in UPPER CASE
There were two main reasons my recomputed hash did not match the hash returned by the SagePay simulator.
1: The vendorName should be in lowercase
2. The AVSCV2 returned by the simulator is urlencoded as "ALL+MATCH". However, the value used to compute the signature is pre the url encoding step and therefore is "ALL MATCH". In your computation of the hah therefore, you will have to urldecode values which may contain spaces.
Hopefully if you adhere to the above two conditions, your generated hash should match the signature generated by the simulator. The code snippets below illustrate the steps required to recompute the Signature correctly.
///
/// Generates the VPS Signature from the parameters of the POST.
///
public virtual string GenerateVPSSignature(string SavedSecurityKey, string vendorName)
{
//These are test values only, gleaned from the SagePay Simulator.
//In your code, you might have your own method of obtaining the Server Registration Transaction
//reponse HTTP Post values from the inputStream.
string VPSTxId ="{6725B26A-64EA-4BD2-9FB0-A4443FAEFDD4}";
string VendorTxCode = "SCC1895636952-VSP-937837270";
string StatusText = "OK";
string TxAuthNo = "6525";
//make sure this is all lowercase
string vendorName = "xxxxxxxxxxxx";
//make sure to change 'ALL+MATCH' below to 'ALL MATCH'
//best way is to use urldecode to reverse the urlencoding from Sagepay
//as is implemented in the computation below
string AvsCv2Text ="ALL+MATCH";
string securityKey = SavedSecurityKey;
string AddressResult = "MATCHED";
string PostCodeResult = "MATCHED";
string Cv2Result = "MATCHED";
string GiftAid = "0";
string _3DStatus = "OK";
string CAVV ="MNJ8TIR4URD75O77NI6D9K";
//Two fields below only present in paypal Transactions
string AddressStatus = "";
string PayerStatus = "";
string CardType = "VISA";
string Last4Digits = "5357";
var builder = new StringBuilder();
builder.Append(VPSTxId);
builder.Append(VendorTxCode);
builder.Append(Status);
builder.Append(TxAuthNo);
builder.Append(vendorName);
builder.Append(HttpUtility.UrlDecode(AvsCv2Text));
builder.Append(SavedSecurityKey);
builder.Append(HttpUtility.UrlDecode(AddressResult));
builder.Append(HttpUtility.UrlDecode(PostCodeResult));
builder.Append(HttpUtility.UrlDecode(Cv2Result));
builder.Append(GiftAid);
builder.Append(_3Dstatus);
builder.Append(CAVV);
builder.Append(HttpUtility.UrlDecode(AddressStatus));
builder.Append(HttpUtility.UrlDecode(PayerStatus));
builder.Append(this.Parameters.CardType);
builder.Append(this.Last4Digits);
var hash = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(builder.ToString(), "MD5");
return hash;
}
Remember to add a using statement to your class file to import the System.Web.Security.FormsAuthentication namespace. The HashPasswordForStoringInConfigFile functiomn used in the code above is in that namespace. Also add a using statement for System.Web since the HttpUtility class resides in that namesoace.
You may then compare the generated hash with your previously saved hash in this manner:
string recomputedSignature = GenerateVPSSignature(securityKey, vendorName);
//Assert
Assert.AreEqual(ResponseVPSSignature, recomputedSignature);
Hope you find this useful.