ColdBox - SagePay VSP Driect Plugin

Now I am getting more to grips with CB I have recreated an ecommerce site using the framework. I have also created a simple plugin for the Coldbox Framework that allows you to integrate SagePay VSP Direct payment system in your website. It seems to work very well the site I used it on has been running the new plugin for few days, taking transactions with no errors.

In case you don't know, SagePay VSP Direct is a method of processing a transaction through the Sage Pay gateway using server to server communication with the customer remaining on your website throughout the whole process.

This plugin will setup, send and handle the response from their gateway. You will need an understanding of Sagepay and a SSL cert to use this plugin. Sage Pay only support UK merchants at the moment!

I will be adding some example of the handler, checkout forms, and cart to a zip download soon at the moment all I have included the plugin CFC code below.

   view plainprintabout
 <cfcomponent name="SagePay"
              hint="SagePay plugin for VSP Direct"
              extends="coldbox.system.plugin"
              output="false"
              cache="true">

 <!------------------------------------------- CONSTRUCTOR ------------------------------------------->    
 <cffunction name="init" access="public" returntype="any" output="false">
  <cfargument name="controller" type="any" required="false" />
  <cfscript>
10   super.Init(arguments.controller);
11   setpluginName("SagePay");
12   setpluginVersion("0.01");
13   setpluginDescription("SagePay plugin for VSP Direct using protocol version: 2.23");
14   //Set which environment to use, simulator,test or live
15  
setEnvironment(gateway=getSetting("gatewayEnvironment"));
16   //Return instance
17  
return this;
18  
</cfscript>
19  </cffunction>
20  <!------------------------------------------- PUBLIC ------------------------------------------->
21  <cffunction name="getPluginDetails" output="false" access="public" returntype="string" hint="Just for information and to test my first plugin function">
22   <!--- Get Event Context --->
23   <cfset var event = controller.getRequestService().getContext()>
24   <!--- Return the name in the event context --->
25   <cfreturn "#getpluginDescription()#. Have a nice day.">
26  </cffunction>
27  <!--- ************************************************************* --->
28  <cffunction name="makeCall" access="public" hint="Make Call" returntype="struct">
29  <!--- ************************************************************* --->
30  <!---<cfargument name="PurchaseURL" type="string" required="yes">--->
31  <cfargument name="VPSProtocol" type="string" required="yes">
32  <cfargument name="vendor" type="string" required="yes">
33  <cfargument name="VendorTxCode" type="string" required="yes">
34  <cfargument name="DefaultCurrency" type="string" default="GBP" required="no">
35  <cfargument name="Amount" type="any" required="yes">
36  <cfargument name="CardHolder" type="string" required="yes">
37  <cfargument name="CardNumber" type="string" required="yes">
38  <cfargument name="DefaultApplyAVSCV2" type="string" required="no" default="0">
39  <cfargument name="Basket" type="string" required="no" default="">
40  <cfargument name="StartDate" type="string" required="no" default="">
41  <cfargument name="ExpiryDate" type="string" required="yes">
42  <cfargument name="DeliveryAddress" type="string" required="yes" default="">
43  <cfargument name="CardType" type="string" required="yes">
44  <cfargument name="BillingPostCode" type="string" required="yes">
45  <cfargument name="DeliveryPostCode" type="string" required="yes">
46  <cfargument name="CustomerName" type="string" required="yes">
47  <cfargument name="ContactNumber" type="string" required="no" default="">
48  <cfargument name="ContactFax" type="string" required="no" default="">
49  <cfargument name="CustomerEmail" type="string" required="no" default="">
50  <cfargument name="ClientIPAddress" type="string" required="no" default="#CGI.REMOTE_ADDR#">
51  <cfargument name="CAVV" type="string" required="no" default="">
52  <cfargument name="XID" type="string" required="no" default="">
53  <cfargument name="ECI" type="string" required="no" default="">
54  <cfargument name="DSecureStatus" type="string" required="no" default="">
55  <cfargument name="CV2" type="string" required="no">
56  <cfargument name="referrerID" type="string" required="no" default="">
57  <cfargument name="DefaultDescription" type="string" required="no" default="Payment From #CustomerName#">
58  <cfargument name="billfirstName" type="string" required="yes">
59  <cfargument name="billlastName" type="string" required="yes">
60  <cfargument name="BillingAddress1" type="string" required="yes">
61  <cfargument name="billingcity" type="string" required="yes">
62  <cfargument name="BillingCountry" type="string" required="no" default="GB">
63  <cfargument name="ISSUENUMBER" type="string" required="no" default="">
64  <!--- ************************************************************* --->
65  <!--- ************************************************************* --->
66   <!---Get the contents of the post from the previous page and split out the variables for sending--->
67   <cfset RequestData = GetHttpRequestData()>
68   <cfset Response = StructNew()>
69   <cfloop list="#RequestData.content#" index="line" delimiters="&">
70   <cfset line = Trim( line )>
71   <cfset StructInsert( Response, Trim( ListFirst( line, "=" ) ), URLDecode(Trim(Mid(line,Find("=",line)+1,len(line)) )) )>
72   </cfloop>
73  <!--- ************************************************************* --->
74  <!---Set the required outgoing properties for the initial HTTPS post to the VPS--->
75  <!--- ************************************************************* --->
76   <!---******************HERE IS WHERE THE ORDER GETS SENT TO PROTX VIA HTTPS*********************** --->
77   <cfhttp url="#GatewaySettings.PurchaseURL#" method="post" delimiter="," throwonerror="no">
78   <!---to combat IIS's compression scheme incompatible with CFHTTP this issue was fixed in MX7 but is back in CF8--->
79   <cfhttpparam type="Header" name="Accept-Encoding" value="deflate;q=0">
80   <cfhttpparam type="Header" name="TE" value="deflate;q=0">
81   <!---end--->
82   <cfhttpparam name="TxType" value="Payment" type="formfield">
83   <cfhttpparam name="VPSProtocol" value="#arguments.VPSProtocol#" type="formfield">
84   <cfhttpparam name="Vendor" value="#arguments.vendor#" type="formfield">
85   <cfhttpparam name="VendorTxCode" value="#arguments.VendorTxCode#" type="formfield">
86   <cfhttpparam name="referrerID" value="#arguments.referrerID#" type="formfield">
87   <cfhttpparam name="Currency" value="#arguments.DefaultCurrency#" type="formfield">
88   <cfhttpparam name="Description" value="#arguments.DefaultDescription#" type="formfield">
89   <cfhttpparam name="Amount" value="#arguments.Amount#" type="formfield">
90   <cfhttpparam name="CardHolder" value="#arguments.CardHolder#" type="formfield">
91   <cfhttpparam name="CardNumber" value="#arguments.CardNumber#" type="formfield">
92   <cfhttpparam name="GiftAidPayment" value="0" type="formfield">
93   <cfhttpparam name="ApplyAVSCV2" value="#arguments.DefaultApplyAVSCV2#" type="formfield">
94   <cfhttpparam name="BillingSurname" value="#arguments.billlastName#" type="formfield">
95   <cfhttpparam name="BillingFirstnames" value="#arguments.billfirstName#" type="formfield">
96   <cfhttpparam name="BillingCity" value="#arguments.billingcity#" type="formfield">
97   <cfhttpparam name="BillingCountry" value="#arguments.BillingCountry#" type="formfield">
98   <cfhttpparam name="DeliverySurname" value="#arguments.billlastName#" type="formfield">
99   <cfhttpparam name="DeliveryFirstnames" value="#arguments.billfirstName#" type="formfield">
100   <cfhttpparam name="DeliveryAddress1" value="#arguments.BillingAddress1#" type="formfield">
101   <cfhttpparam name="DeliveryCity" value="#arguments.BillingAddress1#" type="formfield">
102   <cfhttpparam name="DeliveryCountry" value="#arguments.BillingCountry#" type="formfield">
103   <cfhttpparam name="Basket" value="#arguments.Basket#" type="formfield">
104      <cfif #arguments.StartDate# is not "">
105   <cfhttpparam name="StartDate" value="#arguments.StartDate#" type="formfield">
106   </cfif>
107   <cfif #arguments.ExpiryDate# is not "">
108   <cfhttpparam name="ExpiryDate" value="#arguments.ExpiryDate#" type="formfield">
109   </cfif>
110   <cfif #arguments.DeliveryAddress# is not "">
111   <cfhttpparam name="DeliveryAddress" value="#arguments.DeliveryAddress#" type="formfield">
112   </cfif>
113   <cfhttpparam name="BillingAddress1" value="#arguments.BillingAddress1#" type="formfield">
114   <cfif #arguments.IssueNumber# is not "">
115   <cfhttpparam name="IssueNumber" value="#arguments.IssueNumber#" type="formfield">
116   </cfif>
117   <cfhttpparam name="CV2" value="#arguments.CV2#" type="formfield">
118   <cfhttpparam name="CardType" value="#arguments.CardType#" type="formfield">
119   <cfhttpparam name="BillingPostCode" value="#arguments.BillingPostCode#" type="formfield">
120   <cfif #arguments.DeliveryPostCode# is not "">
121   <cfhttpparam name="DeliveryPostCode" value="#arguments.DeliveryPostCode#" type="formfield">
122   </cfif>
123   <cfhttpparam name="CustomerName" value="#arguments.CustomerName#" type="formfield">
124      <cfif #arguments.ContactNumber# is not "">
125   <cfhttpparam name="ContactNumber" value="#arguments.ContactNumber#" type="formfield">
126   </cfif>
127   <cfif #arguments.ContactFax# is not "">
128   <cfhttpparam name="ContactFax" value="#arguments.ContactFax#" type="formfield">
129   </cfif>
130   <cfhttpparam name="CustomerEmail" value="#arguments.CustomerEmail#" type="formfield">
131   <cfif #arguments.ClientIPAddress# is not "">
132   <cfhttpparam name="ClientIPAddress" value="#arguments.ClientIPAddress#" type="formfield">
133   </cfif>
134   <cfif #arguments.CAVV# is not "">
135   <cfhttpparam name="CAVV" value="#arguments.CAVV#" type="formfield">
136   </cfif>
137   <cfif #arguments.XID# is not "">
138   <cfhttpparam name="XID" value="#arguments.XID#" type="formfield">
139   </cfif>
140   <cfif #arguments.ECI# is not "">
141   <cfhttpparam name="ECI" value="#arguments.ECI#" type="formfield">
142   </cfif>
143   <cfif #arguments.DSecureStatus# is not "">
144   <cfhttpparam name="3DSecureStatus" value="#arguments.arguments.DSecureStatus#" type="formfield">
145   </cfif>
146   </cfhttp>
147   <!--- ********************************END OF HTTPS POST TO PROTX******************************************--->
148   <cfset Response = StructNew()>
149   <!---if http post was ok--->
150   <cfif #cfhttp.statusCode# is "200 OK">
151   <cfloop list="#CFHTTP.FileContent#" index="line" delimiters="#chr(13)#">
152   <cfset line = Trim( line )>
153   <cfset StructInsert( Response, Trim( ListFirst( line, "=" ) ), Trim(Mid(line,Find("=",line)+1,len(line)) ) )>
154   </cfloop>
155   <!---if could not contact gateway--->
156   <cfelse>
157   <cfset StructInsert(Response, "Status", "timeout")>
158   <cfset StructInsert(Response, "StatusDetail", "Timeout Error: could not contact payment gateway or header code was not 200, please contact website owner.")>
159   </cfif>
160   <!---retrun responce--->
161   <cfreturn Response>
162  </cffunction>
163  <!---3D Secure - New responce to sort out with 3D Secure info in it. sort ready to determine next action--->
164  <cffunction name="DSecureCallBack" access="public" hint="Make Call" returntype="struct">
165  <cfargument name="MD" type="string" required="yes">
166  <cfargument name="PARes" type="string" required="yes">
167  <cfscript>
168  var Response= 0;
169  
</cfscript>
170   <cfhttp url="#GatewaySettings.callbackURL#" method="post" delimiter="," throwonerror="no">
171   <cfhttpparam type="Header" name="charset" value="utf-8" />'<!---had some issues on CF8 with compression MX7 seem to be fine--->
172   <cfhttpparam name="MD" value="#arguments.MD#" type="formfield">
173   <cfhttpparam name="PARes" value="#arguments.PARes#" type="formfield">
174   </cfhttp>
175   <cfset Response = StructNew()>
176   <cfloop list="#CFHTTP.FileContent#" index="line" delimiters="#chr(13)#">
177   <cfset line = Trim( line )>
178   <cfset StructInsert( Response, Trim( ListFirst( line, "=" ) ), Trim(Mid(line,Find("=",line)+1,len(line)) ) )>
179   </cfloop>
180   <!---retrun responce--->
181   <cfreturn Response>
182  </cffunction>
183  <!------------------------------------------- PRIVATE ------------------------------------------->
184  <cffunction name="setEnvironment" access="private" hint="Set which gateway ULR's are to be used, simulator, test or live" output="false" returntype="struct">
185  <!--- ************************************************************* --->
186  <cfargument name="gateway" type="string" required="true" default="simulator"/>
187  <!--- ************************************************************* --->
188  <cfset GatewaySettings = StructNew() />
189  <cfscript>
190   if (arguments.gateway is "simulator") {
191   StructInsert(GatewaySettings, "Verify", "false");
192   StructInsert(GatewaySettings, "PurchaseURL", "https://test.sagepay.com/Simulator/VSPDirectGateway.asp");
193  
StructInsert(GatewaySettings, "
RefundURL", "https://test.sagepay.com/Simulator/VSPServerGateway.asp?Service=VendorRefundTx");
194  
StructInsert(GatewaySettings, "ReleaseURL", "https://test.sagepay.com/Simulator/VSPServerGateway.asp?Service=VendorReleaseTx");
195  
StructInsert(GatewaySettings, "
RepeatURL", "https://test.sagepay.com/Simulator/VSPServerGateway.asp?Service=VendorRepeatTx");
196  
StructInsert(GatewaySettings, "callbackURL", "https://test.sagepay.com/Simulator/VSPDirectCallback.asp");
197  
}
198   if (arguments.gateway is "
test") {
199   StructInsert(GatewaySettings, "
Verify", "false");
200   StructInsert(GatewaySettings, "
PurchaseURL", "https://test.sagepay.com/gateway/service/vspdirect-register.vsp");
201  
StructInsert(GatewaySettings, "RefundURL", "https://test.sagepay.com/gateway/service/refund.vsp");
202  
StructInsert(GatewaySettings, "
ReleaseURL", "https://test.sagepay.com/gateway/service/release.vsp");
203  
StructInsert(GatewaySettings, "RepeatURL", "https://test.sagepay.com/gateway/service/repeat.vsp");
204  
StructInsert(GatewaySettings, "
callbackURL", "https://test.sagepay.com/gateway/service/direct3dcallback.vsp");
205  
}
206   if (arguments.gateway is "live") {
207   StructInsert(GatewaySettings, "Verify", "false");
208   StructInsert(GatewaySettings, "PurchaseURL", "https://live.sagepay.com/gateway/service/vspdirect-register.vsp");
209  
StructInsert(GatewaySettings, "
RefundURL", "https://live.sagepay.com/gateway/service/refund.vsp");
210  
StructInsert(GatewaySettings, "ReleaseURL", "https://live.sagepay.com/gateway/service/release.vsp");
211  
StructInsert(GatewaySettings, "
RepeatURL", "https://live.sagepay.com/gateway/service/repeat.vsp");
212  
StructInsert(GatewaySettings, "callbackURL", "https://live.sagepay.com/gateway/service/direct3dcallback.vsp");
213  
}
214  </cfscript>
215   <cfreturn GatewaySettings>
216  </cffunction>
217  
218  </cfcomponent>

How to use

In your ColdBox config setup a new custom setting like so

   view plainprintabout
 <YourSettings>
  <!--simulator,test or live can be used -->
  <Setting name="gatewayEnvironment" value="simulator"/>
  </YourSettings>

Create your payment pages/forms (example will be posted soon)

When you make the call it will look something like this

   view plainprintabout
 //create the object
  SagePayPlugin = getPlugin(plugin="SagePay",customPlugin=true,newInstance=true);
 
 //SagePay gateway CFHTTP call
 response = SagePayPlugin.makeCall(
 VendorTxCode = "", //you transaction code has to be unique for each call
 vendor = "something",//your vendor name
 VPSProtocol = "2.23",
 referrerID = "someone",
10  amount=rc.Amount,//transaction account
11  CardHolder=rc.cardHolder,//customer name and name on card
12  CardNumber=cardNumber,//card number
13  StartDate=rc.startDate1&rc.startDate2,//card start date
14  ExpiryDate=rc.expiryDate1&rc.expiryDate2,//card expiry date
15  DeliveryAddress=rc.billaddress1,//DeliveryAddress same as billing address
16  CardType=rc.cardtype,//type of card being used
17  BillingPostCode=rc.billPostCode,//card post code
18  DeliveryPostCode=rc.billPostCode,//DeliveryAddress same as billing address
19  CustomerName=rc.cardHolder,//card holders name
20  CV2=rc.CV2,//security codebillfirstName=rc.fname,//billing first name
21  billlastName=rc.lname,//billing last name
22  BillingAddress1=rc.billaddress1,//billing address
23  billingcity=rc.billcity//billing city                            
24  );

Handling the response

   view plainprintabout
 if (response.status is "OK" OR response.status IS "ATTEMPTONLY") {
 
 
 }
 
  else if (response.status IS "3DAUTH"){
 
 newresponse = SagePayPlugin.DSecureCallBack(MD=rc.md,
                                             PARes=rc.PARes
10                                              );
11  
12  }
13  
14   else {
15           getPlugin("messagebox").setMessage("error",Response.Status&"<br />"&Response.StatusDetail);     
16       }

Related Blog Entries

TweetBacks
Comments
Tom's Gravatar I have used protx/sagepay vsp server, always been a little put off by vsp direct as you need your own cert. but this looks really good and well put together. It also seems to be the only example I can find in coldfusion at the mo, thanks for this it really will be a help.
# Posted By Tom | 22/06/09 19:57
 

About Me

Glyn Jackson, 27 years old, MD and senior developer of a development firm based in Staffordshire called Newebia Ltd. Academic background in BSc Information System & Internet Commerce. Online marketing expert (EE Ranked) and .NET developer. Has been developing with ColdFusion for 5 years and loves it. "I am not a veteran in ColdFusion but I do work on challenging projects which help me learn more about ColdFusion and if I can contribute to the community in anyway then, it's all good!"

Recommends

  • ColdFusion