Sagepay Access protocol for .NET (VB.NET) using LINQ

Sagepay are a card payment provider. There is a new Sagepay API to manage the actions that are normally accessed through the account management web portal “MySagePay”. At time of writing it is in restricted Beta. As it is not open I feel unable to disclose everything about how it works, but if you are using the beta service with .NET then parts of my wrapper may be useful to you.

To get in on the Beta, go into the Sagepay website Access forum and PM Joe in support who will send you the API documents. If you want my .NET wrapper for Access then I don’t mind sharing but would need to confirm with you that you have been given the Beta documentation first.

LINQ to XML seemed like the best way forward so here goes;

Prepare the XML

I’ve left out the command name, you can replace it if you have the API documentation as you will know what it is. For each call in the API you start with the same XML fragment then append the relevant parameters to it.

Public Shared Function lockUserXMLRequest( _
 ByVal VendorID As String, ByVal UserName As String, ByVal Password As String, _
                      ByVal lockusername As String) As XElement
    If lockusername.Length > 20 Then Throw New ArgumentException("username must be 20 characters maximum", _
         "lockusername", Nothing)
    Dim SagePayRequestXML As XElement = New Credentials("[commandname]", VendorID, UserName, Password)
    SagePayRequestXML.Element("user").AddAfterSelf(<username><%= lockusername %></username>)
    Return HashRequestxml(SagePayRequestXML)
End Function
 
The credentials are passed in for each request as an xElement;
 
Public Class Credentials
    Inherits XElement
 
    Public Sub New(ByVal Command As String, ByVal VendorID As String, _
                   ByVal UserName As String, ByVal Password As String)
        MyBase.New("vspaccess")
        If UserName.Length > 20 Then
            Throw New ArgumentException("UserName maximum of 20 character", "UserName", Nothing)
        End If
        If VendorID.Length > 16 Then
            Throw New ArgumentException("VendorID maximum of 16 character", "VendorID", Nothing)
        End If
        If Password.Length > 32 Then
            Throw New ArgumentException("Password maximum of 32 character", "Password", Nothing)
        End If
        Me.Add(<command><%= Command %></command>)
        Me.Add(<vendor><%= VendorID %></vendor>)
        Me.Add(<user><%= UserName %></user>)
        Me.Add(<password><%= Password %></password>)
    End Sub
End Class

Hash the request

Once you have the XML it is hashed with your vendor password, the password must be removed before the XML is submitted to Sagepay;

Private Shared Function HashRequestxml(ByVal RequestElement As XElement) As XElement
    ''Is there a better way to convert xElement() to string?
    Dim sb As New StringBuilder
    For Each currentElement In RequestElement.Elements
        sb.Append(currentElement.ToString)
    Next
 
    ' Get the "inner xml" of the element
    Dim oReader = RequestElement.CreateReader()
    oReader.MoveToContent()
    Dim asstring = oReader.ReadInnerXml()
 
    'Hash the request and password element together and use the result
    ' as the signature
    Dim signature As XElement = _
            <signature><%= getMd5Hash(asstring) %></signature>
    'Now remove the password, not required now hash generated
    RequestElement.Element("password").Remove()
    RequestElement.Add(signature)
    Return RequestElement
End Function

Note: A post in the Sagepay forum indicates that the final Access may be significantly different to the current one in use. See: Our improved Reporting & Admin API is coming...

LINQ SQL innerxml equivalent

A note to myself, if you have an xElement and want to get just the inner xml then this is the way to do it:

' Get the "inner xml" of the element
Dim oReader = RequestElement.CreateReader()
oReader.MoveToContent()
Dim asstring = oReader.ReadInnerXml()

Sagepay RelatedVPSTxId cannot be found

SagePay are a payment card provider who process cards on behalf of many well known brands. The user posts http posts to the SagePay server to take payments from customer payment cards. Trying a new variation on how we could change our workflow I found a misleading error message.

DEFERRED payment

To validate a card you can take a deferred payment for £1/$1/€1 and then immediately ABORT that payment. This validates the basics of the card information.

Perhaps you don’t ABORT the DEFERRED payment in order to do a REPEATDEFERRED or REPEAT payment against that original transaction -we don’t want to hold the card details do we!

REPEAT payment

This action fails with the error 4028 : The RelatedVPSTxId cannot be found.

Hang on, we have just passed in the correct id for the transaction, what is going on? It turns out, reading the API guide again, that you cannot REPEAT or REPEATDEFERRED until a successful payment has been taken from the transaction. In the case of DEFERRED transaction, this means completing a RELEASE transaction. Looking at the way the web portal, MySagePay lays this information out I guess that the records are held somewhere separated for DEFERRED until they are RELEASED. At this point the transaction become visible to REPEATs. This is a slightly misleading error message in this context.

Multiple PageLoad User Control in Master Page header

PageLoad multiple calls

PageLoad was getting called multiple times for a user control in my Master Page. This had me puzzled for a couple of hours while I was looking in the wrong place for the cause. Then I realised what was going on.

IIS URL rewrite module

The site has a news directory that contains news articles, some static content others dynamic. The master page control in question listed the last few news articles and showed a thumbnail of those articles.

You may start to guess what is coming if I say there was also an IIS url rewriting rule to redirect any request that was not found as a physical file to /news/ and thus return the main news page (default.aspx). This had the effect of redirecting users who were using old links to the new front page.

Missing images

What happened was that development machine had got out of sync with the thumbnail images for this control. Thus when the browser asked for the missing thumbnail, instead IIS did a redirect for that request to /news/. In effect calling the default.aspx for the news directory. As the news front page was sent to the browser instead of the image, the header was rendered as part of this request for default.aspx, resulting in the page header control getting multiple hits depending on how many broken images were present.

This was identified as soon as fiddler was used to look at what the browser was up to. At this point all became very easy to see, several 200 requests for the /news/ url.

The solution for me was to limit the redirect in IIS to be just for .html/.aspx/.htm/.js etc. This solved the problem and properly reported missing images as not found.

Empty image tags

I also noticed a few people talking about empty image tags in server controls causing this same issue. They are correct, even without IIS redirects, there is a redirect in operation, that of serving default.aspx when an empty directory is called. If you have any image tags that render to an empty tag, the browser will unwittingly request default.aspx for the image. This will fire your page events twice or more depending on how many of these links lie in the page. I didn’t see any explanations for this behaviour in those posts so cover it here in case this variation on my issue catches you out.