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.

IIS7 Hijacking my custom error pages

Custom error pages replaced by IIS?

Development Machine W7, Production IIS7 Windows 2008

Just had a sticky problem crop up in production. Custom error pages that used to work no longer work, I don’t know when but sometime our custom ASP.NET error pages have been replaced by stock IIS error pages.

How we use custom error pages

If a website user or search bot attempts to access a product page and the product no longer exists, we send them to a custom error page. The page suggests they try another product or search for alternatives. This page sends back a http 404 status so that the search engines know to drop the accessed URL from the index. It also causes our Google Mini to drop the page from its database too. If the user navigates to another url that has never had content they get a default 404 page returned.

Lets have a look

So you have a great little custom error page like this;

Protected Sub Page_Load1(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        'Ensure a 404 not found sent for SEO purposes
        Response.StatusCode = 404
        Response.TrySkipIisCustomErrors = True
 
    End Sub

However even browsing direct to the page you only get,

IIS7 404 default error page

This is not your crafted custom page, instead this is the IIS7 page kicking in. IIS has put itself in the pipeline to the end user, replacing its own page with in place of the custom page.

IIS custom page configuration

The configuration for this is found in the site Error Pages section;

IIS7 Management Link to Error Pages

If you go into this menu you see all the custom error pages set up in IIS, now select the edit feature settings link on the right hand side;

IIS7 Error Pages Configuration

This is where the behaviour is set and currently this is set to show detail errors for users local to the machine but custom errors for people elsewhere. Note this is not ASP.NET, although the idea is the same, this is above ASP.NET, it is the handling IIS does for these pages.

You do not want normal users to see the detailed errors as it can give away important information about the configuration of your server thus the setting shown is fine and looking at it one would guess it would show our custom pages as we saw by the returned IIS 404 page this is not the case.

IIS7 Custom Errors Settings


So why do we not see our custom pages? Well back in the page code behind shot earlier is the key, you must set

Response.TrySkipIisCustomErrors = True

This tells IIS to get its nose out of what we’re up to and let us get on with it. Trying to browse the custom error page, now we get our custom error page retuned as would be expected in the first case.

Lesson learnt

The real nasty bit of this is that our development machines do not exhibit this behaviour, everything looks fine there. As Rick Strahl says it is another good case for using the staging servers with the production environment so that you test everything.

Other links:

Rick Strahl's Web Log has an almost identical post here

What to expect from IIS7 custom error modulethis link was the one that taught me what I needed to solve the problem.

Custom XML nodes with eConnect for Dynamics GP to call user stored procedures

Problem

Suppose for a moment you wish to transfer credit card authorisation ticket information or other supporting transaction information from a website into your Dynamics GP database.

Solution

You can piggyback on your eConnect integration rather than introducing your own integration. The minds behind eConnect thankfully provided some very easy to use extensibility points in the product.

It is really simple. By adding your own XML node to the XML document that is submitted to eConnect it is possible to move your data to the destination Dynamics database. A stored procedure is called there (stored proc name must match the XML node name) and in that stored procedure you can do anything at all -especially if you are into writing CLR code for SQL server.

Example

In the following example we feed an order created by a website, as an XML document (xmlOrder) into a function. Contained within the xml of teh order are the authorisation details for online credit card authorisations. These details need to end up in a custom table in the Dynamics GP company database. The function returns a LINQ XElement that can then be inserted into the eConnect XML that is subsequently submitted to Dynamics GP via the eConnect API. Lets have a look at the VB.NET.

  1. Public Function CreatePaymentTransactionXML(ByVal xmlOrder As XmlDocument) As XElement
  2.         Dim oCustomNode As XElement = _
  3.         <eConnect_InsertCardPayment>
  4.             <SOPNUMBE><%= CurrentSOPNumber %></SOPNUMBE>
  5.             <SOPTYPE><%= 2 %></SOPTYPE>
  6.             <VPSTxID><%= xmlOrder.SelectSingleNode("/order/Credit-Card-Tx-Code").InnerText %></VPSTxID>
  7.             <TxAuthNo><%= xmlOrder.SelectSingleNode("/order/Credit-Card-Auth-No").InnerText %></TxAuthNo>
  8.             <SecurityKey>9999</SecurityKey>
  9.             <VendorTxCode><%= xmlOrder.SelectSingleNode("/order/Credit-Card-Order-No").InnerText %></VendorTxCode>
  10.             <Amount><%= 0 %></Amount>
  11.             <Currency><%= xmlOrder.SelectSingleNode("/order/Currency-Id").InnerText %></Currency>
  12.             <VendorName><%= "mycompanyname" %></VendorName>
  13.         </eConnect_InsertCardPayment>
  14.         Return oCustomNode
  15.     End Function

 

The example uses the VB.NET literal LINQ xml syntax, it is great for working with these kinds of small XML fragment constructs. The value for each node is pulled out of the sales order XML using XPath statements. Note the example is not yet complete, for example the amount field is tied to a static value of zero but it is functional enough to show the process.

Take notice of the name of the element,“<eConnect_InsertCardPayment>”, this name is also the name of the stored procedure called in the company database. So now a stored procedure needs creating on the company database that will be the end point for this transaction. In this example that stored procedure will insert the data held in the XML to a row in a custom database table.

The custom XML fragment we have just created is inserted into the XML document submitted to eConnect. Add the custom XML node nested inside eConnect transaction type.

The developer can take control of when the procedures are called. The choices are before or after the eConnect procedures have executed. Use the <eConnectProcessInfo> node to do this. The <eConnectProcessInfo> node should always immediately follow the transaction type node. It looks like this:
<eConnectProcessInfo>
   <eConnectProcsRunFirst>TRUE</eConnectProcsRunFirst>
</eConnectProcessInfo>

One of these process info nodes can be added per transaction in the XML document submitted, executing this in VB code looks like this;
  1. Dim oeConnectProcessInfo As New eConnectProcessInfo
  2.         oeConnectProcessInfo.eConnectProcsRunFirst = "TRUE"
  3.         oeConnectType.SOPTransactionType(0).eConnectProcessInfo = oeConnectProcessInfo

Dynamics GP Company Custom Stored Procedure

Each XML element within our custom XML fragment is passed into the stored procedure as a parameter with the same name as shown below (proceeded with @I_v for input variable). You should therefore make certain your elements are named uniquely. Within the stored procedure, as a developer you can achieve what you want. In this case the payment card fulfilment information is merely inserted into a table for later processing or reporting.

CREATE PROCEDURE [dbo].[eConnect_InsertCardPayment]
( @I_vSOPNUMBE varchar(31),
  @I_vSOPTYPE smallint,
  @I_vVPSTxID nvarchar(50),
  @I_vTxAuthNo nvarchar(50),
  @I_vSecurityKey nvarchar(50),
  @I_vVendorTxCode nvarchar(40), 
  @I_vAmount money =0,
  @I_vCurrency char(3),
  @I_vVendorName varchar(20),
  @O_iErrorState int output, /* Return value: 0 = No Errors, Any Errors > 0 */
  @oErrString varchar(255) output /* Return Error Code List */
  )
  AS
 
  declare 
 
    @O_oErrorState int,
    @iError int,
    @iStatus smallint,
    @iAddCodeErrState int
 
/*********************** Initialize locals *****************************************************/
select    @O_iErrorState = 0,
    @oErrString = '',
    @iStatus = 0,
    @iAddCodeErrState = 0
 
INSERT INTO [my_PaymentCardTransaction]
           ([TxType]
           ,[Status]
           ,[StatusDetail]
           ,[VPSTxID]
           ,[TxAuthNo]
           ,[SecurityKey]
           ,[VendorTxCode]
           ,[Amount]
           ,[Currency]
           ,[SOPTYPE]
           ,[SOPNUMBE]
           ,[VendorName]
           ,[ModifiedDate]
           ,[CreatedDate]
           )
        VALUES
           ('DEFERRED',
           'OK',
           '',
           @I_vVPSTxID,
           @I_vTxAuthNo,
           @I_vSecurityKey,
           @I_vVendorTxCode,
           @I_vAmount,
           @I_vCurrency,
           @I_vSOPTYPE, 
           @I_vSOPNUMBE,
           @I_vVendorName,
           getdate(),
           getdate()          
 
           )
  return (@O_iErrorState)

 

 

 

 

But wait there is more

For all the eConnect operations there is a rudimentary “event model” made available via stored procedures. For example inserting a SOP Sales Document will cause the taSOPHdrIvcInsertPre and taSOPHdrIvcInsertPost procedures in the company database to be called before the record is inserted into SOP10100 and after respectively.
image

You are allowed to open up these procedures and code your own functionality in there. Perhaps you have some third party modifications that don’t natively support eConnect. You could set some database values in these third party tables using the taSOPHdrIvcInsertPost procedure, after the sales order has been inserted. As this is happening after it has been inserted you have all the defaulted values from the sales order document type/classes available to you in the procedure together with the data inserted from the supplied eConnect XML document.

Perhaps you need to validate the sales order information, say for example checking the customer has not supplied a duplicate PO reference to you. This could be done in the taSOPHdrIvcInsertPre, from where you could raise an error should validation fail.

Another useful technique is to combine the custom XML node technique discussed earlier with this event model. Use the custom node to insert the data into a cache table then pull it out and use it in earnest in the post event of the document transaction once all the defaults have been set. One benefit may be to keep the bulk of the sql in one place for clarity and ease of maintenance.

Another idea is to send an email to the sales team from inside the post stored procedure of the transaction, each time a sales order is created by eConnect. Sales staff now get a neat notification that there is a web sales order to process. If you are into writing CLR stuff in SQL you could go almost anywhere with this. Perhaps an alternative to the notification email would be to create a Microsoft Sharepoint task for someone to deal with the order and spin off a workflow to keep the process on track.

Go ahead use your imagination in our implantation, like I have in ours.

Further references

Recommend reading are the MSDN pages on this subject to get the full understanding and further examples of options for eConnectProcessInfo. Full schemas for eConnect are also provided there.

MSDN: eConnectProcessInfo http://msdn.microsoft.com/en-us/library/bb648359.aspx

MSDN Custom XML Nodes: http://msdn.microsoft.com/en-us/library/bb625125.aspx