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 plain print about
1<cfcomponent name="SagePay"
2             hint="SagePay plugin for VSP Direct"
3             extends="coldbox.system.plugin"
4             output="false"
5             cache="true">

6<!------------------------------------------- CONSTRUCTOR ------------------------------------------->    
7<cffunction name="init" access="public" returntype="any" output="false">
8 <cfargument name="controller" type="any" required="false" />
9 <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>
168var 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 plain print about
1<YourSettings>
2 <!--simulator,test or live can be used -->
3 <Setting name="gatewayEnvironment" value="simulator"/>
4 </YourSettings>

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

When you make the call it will look something like this

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

Handling the response

view plain print about
1if (response.status is "OK" OR response.status IS "ATTEMPTONLY") {
2
3
4}
5
6 else if (response.status IS "3DAUTH"){
7
8newresponse = SagePayPlugin.DSecureCallBack(MD=rc.md,
9                                            PARes=rc.PARes
10                                            );
11
12}
13
14 else {
15         getPlugin("messagebox").setMessage("error",Response.Status&"<br />"&Response.StatusDetail);     
16     }

Related Blog Entries

Jun21

Comments 5

  1. Tom's Gravatar # Posted By Tom
    22/06/09 19:57

    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.

  1. ZAC's Gravatar # Posted By ZAC
    06/03/10 20:23

    i like this, ive been looking for some help with this integration so i found this just at the right time.

    would you say you example is now complete and working?

    what do you mean when you say "ColdBox"?

  1. Cloudspotting's Gravatar # Posted By Cloudspotting
    04/08/10 14:27

    Great plugin, thanks very much! There is also this blog post for anyone looking to integrate Sage Pay with ColdFusion using the Form Integration process:

    http://blog.cloudspotting.co.uk/2010/06/11/integra...

  1. Max's Gravatar # Posted By Max
    04/04/11 18:50

    Is this code still valid and supporting 3D Secure?

  1. Glyn Jackson's Gravatar # Posted By Glyn Jackson
    15/06/11 13:15

    Depends on what version you are using, 3D secure is included but not tested. When I have some time I will be updating the plugin