* Add enclair_db option to UserDB.pm. Allows logging of enclair password
[interchange.git] / lib / Vend / Payment / SagePay.pm
1 # Vend::Payment::SagePay - Interchange Sagepay support
2 #
3 # SagePay.pm, v 0.8.7, May 2009
4 #
5 # Copyright (C) 2009 Zolotek Resources Ltd. All rights reserved.
6 #
7 # Author: Lyn St George <info@zolotek.net, http://www.zolotek.net>
8 # Based on original code by Mike Heins <mheins@perusion.com> and others.
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public Licence as published by
11 # the Free Software Foundation; either version 2 of the Licence, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public Licence for more details.
18 #
19 # You should have received a copy of the GNU General Public
20 # Licence along with this program; if not, write to the Free
21 # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
22 # MA  02111-1307  USA.
23
24
25
26 package Vend::Payment::SagePay;
27
28 =head1 Interchange Sagepay Support
29
30 Vend::Payment::SagePay $Revision: 0.8.7 $
31
32 http://kiwi.zolotek.net is the home page with the latest version.
33
34 =head1 This package is for the 'SagePay Direct' payment system.
35
36 Note that their 'Direct' system is the only one which leaves the customer on
37 your own site and takes payment in real time. Their other systems, eg Terminal
38 or Server, do not require this module.
39
40
41
42 =head1 Quick Start Summary
43
44 1 Place this module in <IC_root>/lib/Vend/Payment/SagePay.pm
45
46 2 Call it in interchange.cfg with:
47     Require module Vend::Payment::SagePay
48
49 3 Add into variable.txt (tab separated):
50     MV_PAYMENT_MODE   sagepay
51   Add a new route into catalog.cfg (options for the last entry in parentheses):
52     Route sagepay id YourSagePayID
53     Route sagepay host live.sagepay.com (test.sagepay.com)
54     Route sagepay currency GBP (USD, EUR, others, defaults to GBP)
55     Route sagepay txtype PAYMENT (AUTHENTICATE, DEFERRED)
56     Route sagepay available yes (no, empty)
57     Route sagepay logzero yes (no, empty)
58     Route sagepay logorder yes (no, empty)
59     Route sagepay logsagepay yes (no, empty)
60     Route sagepay applyavscv2 '0': if enabled then check, and if rules apply use.
61                     '1': force checks even if not enabled; if rules apply use.
62                     '2': force NO checks even if enabled on account.
63                     '3': force checks even if not enabled; do NOT apply rules.
64     Route sagepay giftaidpayment 0 (1 to donate tax to Gift Aid)
65
66 4 Create a new locale setting for en_GB as noted in "item currency" below, and copy the
67 public space interchange/en_US/ directory to a new interchange/en_GB/ one. Ensure that any
68 other locales you might use have a correctly named directory as well. Ensure that this locale
69 is found in your version of locale.txt (and set up GB as opposed to US language strings to taste).
70
71 5 Create entry boxes on your checkout page for: 'mv_credit_card_issue_number', 'mv_credit_card_start_month',
72 'mv_credit_card_start_year', 'mv_credit_card_type' and  'mv_credit_card_cvv2'.
73
74 6 The new fields in API 2.23 are: BillingAddress, BillingPostCode, DeliveryAddress, DeliveryPostCode,
75 BillingFirstnames, BillingSurname, DeliveryFirstnames, DeliverySurname, ContactNumber,ContactFax,CustomerEmail.
76 CustomerName has been removed. Billing and Delivery State must be sent if the destination country is the US, otherwise
77 they are not required. State must be only 2 letters if sent. Other fields may default to a space if there
78 is no proper value to send, though this may conflict with your AVS checking rules. SagePay currently 
79 accept a space as of time of writing - if they change this without changing the API version then send
80 either a series of '0' or '-' characters to stop their error messages. 
81
82 7. Add a page in pages/ord/, tdsfinal.html, being a minimal page with only the header and side bar,
83 and in the middle of the page put:
84 [if scratch acsurl]
85           <tr>
86                 <td align=center height=600 valign=middle colspan=2>
87                   <iframe src="__CGI_URL__/ord/tdsauth.html" frameborder=0 width=600 height=600></iframe>
88                 </td>
89           </tr>
90 [/if]
91
92 Add a page in pages/ord/, tdsauth.html, consisting of this:
93 <body onload="document.form.submit();">
94 <FORM name="form" action="[scratchd acsurl]" method="POST" />
95 <input type="hidden" name="PaReq" value="[scratch pareq]" />
96 <input type="hidden" name="TermUrl" value="[scratch termurl]" />
97 <input type="hidden" name="MD" value="[scratch md]" />
98 </form>
99 </body>
100 along with whatever <noscript> equivalent you want. This will retrieve the bank's page within the iframe.
101
102 Add a page in pages/ord/, tdsreturn.html, consisting of this:
103         [charge route="sagepay" sagepayrequest="3dsreturn"]
104         <p>
105            <blockquote>
106         <font color="__CONTRAST__">
107                 [error all=1 keep=1 show_error=1 show_label=1 joiner="<br>"]
108         </font>
109        </blockquote>
110
111 The iframe in 'tdsfinal' will be populated with the contents of 'tdsauth', and the javascript will
112 automatically display the bank's authentication page. When the customer clicks 'Submit' at the bank's
113 page, the iframe contents will be replaced with the 'tdsreturn' page, which will complete the route 
114 and display the receipt inside the iframe. If the customer clicks 'cancel' at the bank, then this 
115 'tdsreturn' page will stay put and display whatever message you have put there along with the error message. 
116 The value of [scratch tds] is set true for a 3DSecure transaction only, so can be used for messages
117 etc on the receipt page. 
118
119 8. When running a card through 3DSecure, the route is run twice: firstly to Sagepay who check whether or
120 not the card is part of 3DSecure - if it is they send the customer to the bank's authentication page
121 and upon returning from that the route must be run a second time to send the authentication results to
122 Sagepay. The second run is initiated from the 'ord/tdsreturn' page, not from etc/log_transaction as it normally
123 would be. To handle this change to the normal system flow you need to alter log_transaction to make the 
124 call to the payment module conditional,ie, wrap the following code around the "[charge route...]" call 
125 found in ln 172 (or nearby):
126         [if scratchd mstatus eq success]
127         [tmp name="charge_succeed"][scratch order_id][/tmp]
128         [else]
129         [tmp name="charge_succeed"][charge route="[var MV_PAYMENT_MODE]" amount="[scratch tmp_remaining]" order_id="[value mv_transaction_id]"][/tmp]
130         [/else]
131         [/if]
132 If the first call to Sagepay returns a request to send the customer to the 3DSecure server, then IC will 
133 write a payment route error to the error log prior to sending the customer there. This error stops the
134 route completing and lets the 3DSecure process proceed as it should. This error is not raised if the card
135 is not part of 3DSecure, and instead the route completes as it normally would. 
136
137 Also add this line just after '&final = yes' near the end of the credit_card section of etc/profiles.order:
138         &set=mv_payment_route sagepay
139
140 9. Add these new fields into log_transaction, to record the values returned from Sagepay (these will be
141 key in identifying transactions and problems in any dispute with them):
142
143 mv_credit_card_type: [calc]$Session->{payment_result}{CardType}[/calc]
144 mv_credit_card_issue_number: [value mv_credit_card_issue_number]
145 txtype:  [calc]$Session->{payment_result}{TxType};[/calc]
146 vpstxid: [calc]$Session->{payment_result}{VPSTxID};[/calc]
147 txauthno: [calc]$Session->{payment_result}{TxAuthNo};[/calc]
148 securitykey: [calc]$Session->{payment_result}{SecurityKey};[/calc]
149 vendortxcode:  [calc]$Session->{payment_result}{VendorTxCode};[/calc]
150 avscv2: [calc]$Session->{payment_result}{AVSCV2};[/calc]
151 addressresult:[calc]$Session->{payment_result}{AddressResult};[/calc]
152 postcoderesult: [calc]$Session->{payment_result}{PostCodeResult};[/calc]
153 cv2result: [calc]$Session->{payment_result}{CV2Result};[/calc]
154 securestatus:[calc]$Session->{payment_result}{SecureStatus};[/calc]
155 pares: [calc]$Session->{payment_result}{PaRes};[/calc]
156 md: [calc]$Session->{payment_result}{MD};[/calc]
157 cavv: [calc]$Session->{payment_result}{CAVV};[/calc]
158 and add these into your MySQL or Postgres transactions table, as type varchar(128) except for 'pares'
159 which should be type 'text'.
160
161 Note that there is no 'TxAuthNo' returned for a successful AUTHENTICATE.
162
163 =head1 PREREQUISITES
164
165   Net::SSLeay
166     or
167   LWP::UserAgent and Crypt::SSLeay
168
169   wget - a recent version built with SSL and supporting the 'connect' timeout function.
170
171
172 =head1 DESCRIPTION
173
174 The Vend::Payment::SagePay module implements the SagePay() routine for use with
175 Interchange. It is _not_ compatible on a call level with the other Interchange
176 payment modules - SagePay does things rather differently. 
177
178 To enable this module, place this directive in C<interchange.cfg>:
179
180     Require module Vend::Payment::SagePay
181
182 This I<must> be in interchange.cfg or a file included from it.
183
184 Make sure CreditCardAuto is off (default in Interchange demos).
185
186
187 =head1 The active settings.
188
189 The module uses several of the standard settings from the Interchange payment routes.
190 Any such setting, as a general rule, is obtained first from the tag/call options on
191 a page, then from an Interchange order Route named for the mode in catalog.cfg,
192 then a default global payment variable in products/variable.txt, and finally in
193 some cases a default will be hard-coded into the module.
194
195 =over
196
197 =item Mode
198
199 The mode can be named anything, but the C<gateway> parameter must be set
200 to C<sagepay>. To make it the default payment gateway for all credit card
201 transactions in a specific catalog, you can set in C<catalog.cfg>:
202
203     Variable   MV_PAYMENT_MODE  sagepay
204 or in variable.txt:
205     MV_PAYMENT_MODE sagepay (tab separated)
206
207 if you want this to cooperate with other payment systems, eg PaypalExpress, then see the documentation
208 that comes with that system - it should be fully explained there.
209
210
211 =item id
212
213 Your SagePay vendor ID, supplied by SagePay when you sign up. Various ways to state this:
214 in variable.txt:
215     MV_PAYMENT_ID   YourSagePayID Payment
216 or in catalog.cfg either of:
217     Route sagepay id YourSagePayID
218     Variable MV_PAYMENT_ID      YourSagePayID
219 or on the page
220     [charge route=sagepay id=YourSagePayID]
221
222
223 =item txtype
224
225 The transaction type is one of: PAYMENT, AUTHENTICATE, DEFERRED for an initial purchase
226 through the catalogue, and then can be one of: AUTHORISE, REFUND, RELEASE, VOID, ABORT for payment
227 operations through the virtual terminal.
228
229 The transaction type is taken firstly from a dynamic variable in the page, meant
230 primarily for use with the 'virtual payment terminal', viz: 'transtype' in a select box
231 though this could usefully be taken from a possible entry in the products database
232 if you have different products to be sold on different terms; then falling back to
233 a 'Route txtype PAYMENT' entry in catalog.cfg; then falling back to a global
234 variable in variable.txt, eg 'MV_PAYMENT_TXTYPE PAYMENT Payment'; and finally
235 defaulting to 'PAYMENT' hard-coded into the module. This variable is returned to
236 the module and logged using the value returned from SagePay, rather than a value from
237 the page which possibly may not exist.
238
239
240 =item available
241
242 If 'yes', then the module will check that the gateway is responding before sending the transaction.
243 If it fails to respond within 9 seconds, then the module will go 'off line' and log the transaction
244 as though this module had not been called. It will also log the txtype as 'OFFLINE' so that you
245 know you have to put the transaction through manually later (you will need to capture the card
246 number to do this). The point of this is that your customer has the transaction done and dusted,
247 rather than being told to 'try again later' and leaving for ever. If not explicitly 'yes',
248 defaults to 'no'. NB: if you set this to 'yes', then add into the etc/report that is sent to you:
249 Txtype = [calc]$Session->{payment_result}{TxType};[/calc]. Note that you need to have
250 a recent version of wget which supports '--connect-timeout' to run this check. Note also that,
251 as this transaction has not been logged anywhere on the SagePay server, you cannot use their
252 terminal to process it. You must use a virtual terminal which includes a function for this purpose,
253 and updates the existing order number with the new payment information returned from SagePay. Note
254 further that if you have SagePay set up to require the CV2 value, then virtual terminal should disable
255 CV2 checking at run-time by default for such a transaction (logging the CV2 value breaks Visa/MC
256 rules and so it can't be legally available for this process).
257
258
259 =item logzero
260
261 If 'yes', then the module will log a transaction even if the amount sent is zero (which the
262 gateway would normally reject). The point of this is to allow a zero amount in the middle of a
263 subscription billing series for audit purposes. If not explicitly 'yes', defaults to 'no'.
264 Note: this is only useful if you are using an invoicing system or the Payment Terminal, both of which
265 by-pass the normal IC processes. IC will allow an item to be processed at zero total price but simply
266 bypasses the gateway when doing so.
267
268
269 =item logempty
270 If 'yes, then if the response from SagePay is read as empty (ie, zero bytes) then the module will use the
271 VendorTxID to check on the Sagepay txstatus page to see if that transaction has been logged. If it has then
272 the result found on that page will be used to push the result to either success or failure and log accordingly.
273 There are two markers set to warn of this:
274 $Session->{payment_result}{TxType} will be NULL,
275 $Session->{payment_result}{StatusDetail} will be: 'UNKNOWN status - check with SagePay before dispatching goods'
276 and you should include these into the report emailed to you. It will also call a logorder Usertag to log
277 a backup of the order: if you don't already have this then get it from ftp.zolotek.net/mv/logorder.tag
278
279 If the result is not found on that txstatus page then the result is forced to 'failure' and the transaction 
280 shown as failed to the customer. 
281
282
283 =item card_type
284
285 SagePay requires that the card type be sent. Valid types are: VISA, MC, AMEX, DELTA, SOLO, MAESTRO, UKE,
286 JCB, DINERS (UKE is Visa Electron issued in the UK).
287
288 You may display a select box on the checkout page like so:
289
290               <select name=mv_credit_card_type>
291           [loop
292                   option=mv_credit_card_type
293                   acclist=1
294                   list=|
295 VISA=Visa,
296 MC=MasterCard,
297 SOLO=Solo,
298 DELTA=Delta,
299 MAESTRO=Maestro,
300 AMEX=Amex,
301 UKE=Electron,
302 JCB=JCB,
303 DINERS=Diners|]
304           <option value="[loop-code]"> [loop-param label]
305           [/loop]
306           </select>
307
308
309 =item currency
310
311 SagePay requires that a currency code be sent, using the 3 letter ISO currency code standard,
312 eg, GBP, EUR, USD. The value is taken firstly from either a page setting or a
313 possible value in the products database, viz 'iso_currency_code'; then falling back
314 to the locale setting - for this you need to add to locale.txt:
315
316     code    en_GB   en_EUR  en_US
317     iso_currency_code   GBP EUR USD
318
319 It then falls back to a 'Route sagepay currency EUR' type entry in catalog.cfg;
320 then falls back to a global variable (eg MV_PAYMENT_CURRENCY EUR Payment); and
321 finally defaults to GBP hard-coded into the module. This variable is returned to
322 the module and logged using the value returned from SagePay, rather than a value from
323 the page which possibly may not exist.
324
325
326 =item cvv2
327
328 This is sent to SagePay as mv_credit_card_cvv2. Put this on the checkout page:
329
330     <b>CVV2: &nbsp; <input type=text name=mv_credit_card_cvv2 size=6></b>
331
332 but note that under Card rules you must not log this value anywhere.
333
334
335 =item issue_number
336
337 This is used for some debit cards, and taken from an input box on the checkout page:
338
339     Card issue number: <input type=text name=mv_credit_card_issue_number value='' size=6>
340
341 =item mvccStartDate
342
343 This is used for some debit cards, and is taken from select boxes on the
344 checkout page in a similar style to those for the card expiry date. The labels to be
345 used are: 'mv_credit_card_start_month', 'mv_credit_card_start_year'. Eg:
346
347                   <select name=mv_credit_card_start_year>
348                   [loop option=start_date_year lr=1 list=`
349                   my $year = $Tag->time( '', { format => '%Y' }, '%Y' );
350                   my $out = '';
351                   for ($year - 7 .. $year) {
352                                   /\d\d(\d\d)/;
353                                   $last_two = $1;
354                                   $out .= "$last_two\t$_\n";
355                   }
356                   return $out;
357                   `]
358                   <option value="[loop-code]"> [loop-pos 1]
359                   [/loop]
360                   </select>
361
362 Make the select box for the start month a copy of the existing one for the expiry month, but with
363 the label changed and the addition of 
364 = --select --, 
365 as the first entry. This intentionally returns nothing for that selection and prevents the StartDate being sent.
366
367
368 =item SagePay API v2.23 extra functions
369 ApplyAVSCV2 set to:
370         0 = If AVS/CV2 enabled then check them.  If rules apply, use rules. (default)
371         1 = Force AVS/CV2 checks even if not enabled for the account. If rules apply, use rules.
372         2 = Force NO AVS/CV2 checks even if enabled on account.
373         3 = Force AVS/CV2 checks even if not enabled for the account but DON'T apply any rules.
374         You may pass this value from the page as 'applyavscv2' to override the payment route setting.
375         They also have Paypal integrated into this version, but I have no interest in implementing Paypal 
376         through Sagepay. There is a separate PaypalExpress module for that. 
377
378 ContactFax: optional
379 GiftAidPayment: set to -
380         0 = This transaction is not a Gift Aid charitable donation(default)
381         1 = This payment is a Gift Aid charitable donation and the customer has AGREED to donate the tax.
382         You may pass this value from the page as 'giftaidpayment'
383         
384 ClientIPAddress: will show in SagePay reports, and they will attempt to Geo-locate the IP.
385
386
387 =item AVSCV2
388 SagePay do not use your rulebase or return any checks for these when using 3ds and AUTHORISE. As this data
389 is essential for many business models you should use DEFERRED instead. While thought was given to
390 running a PAYMENT and VOID for Â£1 first, simply to get the AVSCV2 results, this cannot be done
391 with Maestro cards which legally must go through 3ds and so I have abandoned the idea. 
392
393
394 =item Encrypted email with card info
395
396 If you want to add the extra fields (issue no, start date) to the PGP message
397 emailed back to you, then set the following in catalog.cfg:
398
399 Variable<tab>MV_CREDIT_CARD_INFO_TEMPLATE Card type: {MV_CREDIT_CARD_TYPE}; Card no: {MV_CREDIT_CARD_NUMBER}; Expiry: {MV_CREDIT_CARD_EXP_MONTH}/{MV_CREDIT_CARD_EXP_YEAR}; Issue no: {MV_CREDIT_CARD_ISSUE_NUMBER}; StartDate: {MV_CREDIT_CARD_START_MONTH}/{MV_CREDIT_CARD_START_YEAR}
400
401
402 =item testing
403
404 The SagePay test site is test.sagepay.com, and their live site is
405 live.sagepay.com. Enable one of these in MV_PAYMENT_HOST in variable.txt
406 (*without* any leading https://) or as 'Route sagepay host test.sagepay.com' in
407 catalog.cfg. Be aware that the test site is not an exact replica of the live site, and errors there
408 can be misleading. In particular the "SecureStatus" returned from the test site is
409 liable to be 'NOTAUTHED' when the live site will return 'OK'.
410
411
412 =item methods
413
414 An AUTHENTICATE will check that the card is not stolen and contains sufficient funds.
415 SagePay will keep the details, so that you may settle against this a month or more
416 later. Against an AUTHENTICATE you may do an AUTHORISE (which settles the transaction).
417
418 A DEFERRED will place a shadow ('block') on the funds for seven days (or so, depending
419 on the acquiring bank). Against a DEFERRED you may do a RELEASE to settle the transaction.
420
421 A PAYMENT will take the funds immediately. Against a PAYMENT, you may do a
422 REFUND or REPEAT.
423
424 A REPEAT may be performed against an AUTHORISE or a PAYMENT. This will re-check and
425 take the funds in real time. You may then REPEAT a REPEAT, eg for regular
426 subscriptions. As you need to send the amount and currency with each REPEAT, you
427 may vary the amount of the REPEAT to suit a variation in subscription fees.
428
429 A RELEASE is performed to settle a DEFERRED. Payment of the originally specified
430 amount is guaranteed if the RELEASE is performed within the seven days for which
431 the card-holder's funds are 'blocked'.
432
433 A REFUND may be performed against a PAYMENT, RELEASE, or REPEAT. It may be for a
434 partial amount or the entire amount, and may be repeated with several partial
435 REFUNDs so long as the total does not exceed the original amount.
436
437 A DIRECTREFUND sends funds from your registered bank account to the nominated credit card.
438 This does not need to refer to any previous transaction codes, and is useful if you need to
439 make a refund but the customer's card has changed or the original purchase was not made by card.
440
441 =back
442
443 =head2 Troubleshooting
444
445 Try a sale with  any other test number given by SagePay, eg:
446         Visa VISA 4929 0000 0000 6
447     Mastercard  MC 5404 0000 0000 0001
448     Delta DELTA 4462 0000 0000 0000 0003
449     Visa Electron UK Debit      UKE     4917300000000008
450     Solo SOLO 6334 9000 0000 0000 0005 issue no 1
451     Switch (UK Maestro) MAESTRO 5641 8200 0000 0005 issue no 01.
452     Maestro MAESTRO 300000000000000004
453         AmericanExpress AMEX    3742 0000 0000 004
454
455
456 You need these following values to ensure a positive response:
457 CV2: 123
458 Billing Address: 88
459 Billing PostCode: 412
460 and the password at their test server is 'password'.
461
462
463 If nothing works:
464
465 =over 4
466
467 =item *
468
469 Make sure you "Require"d the module in interchange.cfg:
470
471     Require module Vend::Payment::SagePay
472
473 =item *
474
475 Make sure either Net::SSLeay or Crypt::SSLeay and LWP::UserAgent are installed
476 and working. You can test to see whether your Perl thinks they are:
477
478     perl -MNet::SSLeay -e 'print "It works\n"'
479 or
480     perl -MLWP::UserAgent -MCrypt::SSLeay -e 'print "It works\n"'
481
482 If either one prints "It works." and returns to the prompt you should be OK
483 (presuming they are in working order otherwise).
484
485 =item *
486
487 Check the error logs, both catalogue and global. Make sure you set your payment
488 parameters properly. Try an order, then put this code in a page:
489
490     <XMP>
491     [calc]
492         my $string = $Tag->uneval( { ref => $Session->{payment_result} });
493         $string =~ s/{/{\n/;
494         $string =~ s/,/,\n/g;
495         return $string;
496     [/calc]
497     </XMP>
498
499 That should show what happened.
500
501 =item *
502
503 If you have a PGP/GPG failure when placing an order through your catalogue
504 then this may cause the module to be immediately re-run. As the first run would
505 have been successful, meaning that both the basket and the credit card information
506 would have been emptied, the second run will fail. The likely error message within
507 the catalogue will be:
508 "Can't figure out credit card expiration". Fixing PGP/GPG will fix this error.
509
510 If you get the same error message within the Virtual Terminal, then you haven't
511 set the order route as noted above.
512
513
514 =item *
515
516 If all else fails, Zolotek and other consultants are available to help
517 with integration for a fee.
518
519 =back
520
521
522 =head1 AUTHORS
523
524 Lyn St George <info@zolotek.net>, based on original code by Mike Heins
525 <mike@perusion.com> and others.
526
527 =head2 CREDITS
528 Hillary Corney (designersilversmiths.co.uk), Jamie Neil (versado.net),
529 Andy Mayer (andymayer.net) for testing and suggestions.
530
531
532
533 =cut
534
535 BEGIN {
536
537     my $selected;
538     eval {
539         package Vend::Payment;
540         require Net::SSLeay;
541         import Net::SSLeay qw(post_https make_form make_headers);
542         $selected = "Net::SSLeay";
543     };
544
545     $Vend::Payment::Have_Net_SSLeay = 1 unless $@;
546
547     unless ($Vend::Payment::Have_Net_SSLeay) {
548
549         eval {
550             package Vend::Payment;
551             require LWP::UserAgent;
552             require HTTP::Request::Common;
553             require Crypt::SSLeay;
554             require CGI;
555             require Encode;
556              import Encode qw(encode decode);
557              import HTTP::Request::Common qw(POST);
558             $selected = "LWP and Crypt::SSLeay";
559         };
560
561         $Vend::Payment::Have_LWP = 1 unless $@;
562     }
563
564     unless ($Vend::Payment::Have_Net_SSLeay or $Vend::Payment::Have_LWP) {
565         die __PACKAGE__ . " requires Net::SSLeay or Crypt::SSLeay";
566     }
567
568 ::logGlobal("%s 0.8.8 payment module initialised, using %s", __PACKAGE__, $selected)
569         unless $Vend::Quiet;
570
571 }
572
573 package Vend::Payment;
574 use strict; 
575
576 sub sagepay {
577         
578         my $sagepaystart = time;
579         my $date = $Tag->time({ body => "%Y%m%d%H%M%S" });
580         my $sagepaydate = $Tag->time({ body => "%A %d %B %Y, %k:%M:%S, %Z" });
581         
582         my ($vendor, $amount, $actual, $opt, $sagepayrequest, $page, $vendorTxCode, $pan, $cardType);
583         
584         # Amount sent to SagePay, in 2 decimal places with cruft removed.
585         # Defaults to 'amount' from log_transaction or an invoicing system, falling back to IC input
586            $amount =  $::Values->{amount} || charge_param('amount') || Vend::Interpolate::total_cost();
587            $amount =~ s/^\D*//g;
588            $amount =~ s/\,//g;
589            $amount =  sprintf '%.2f', $amount;
590         
591         # Transaction type sent to SagePay.
592         my $txtype      = $::Values->{transtype} || charge_param('txtype') || $::Variable->{MV_PAYMENT_TRANSACTION} ||'PAYMENT';
593         my $vpsprotocol = '2.23';
594         my $accountType = $::Values->{account_type} || charge_param('account_type') || 'E';
595         my $payID       = $::Values->{inv_no} || $::Session->{mv_transaction_id} || $::Session->{id}.$amount;
596         my $logorder    = charge_param('logorder') || 'no'; # Set to 'yes' or '1' to log basket plus data useful when arguing with SagePay over empty responses
597         my $logsagepay  = charge_param('logsagepay') || 'no'; # Set to yes or 1 to log sagepay activity for debugging
598         my $logzero     = charge_param('logzero')   || 'no';
599         my $available   = $::Values->{available} || charge_param('available')  || 'no';
600         my $description = "$::Values->{company} $::Values->{fname} $::Values->{lname}";
601            $description = substr($description,0,99);
602         my $apply3ds    = $::Values->{apply3ds} ||  charge_param('apply3ds') || '0'; # '2' will turn 3ds off, '0' is default live variant
603         my $applyAVSCV2 = $::Values->{applyavscv2} || charge_param('applyavscv2') || '0';
604         my $termurl     = charge_param('returnurl') || "$::Variable->{SECURE_SERVER}$::Variable->{CGI_URL}/ord/tdsreturn";
605         my $tdscallback = charge_param('tdscallback') || '/gateway/service/direct3dcallback.vsp';
606         my $checkouturl = charge_param('checkouturl') || "$::Variable->{SECURE_SERVER}$::Variable->{CGI_URL}/ord/checkout";
607         my $checkstatus = charge_param('check_status') || '1';
608         my $checkstatusurl = charge_param('check_status_url') || '/TxStatus/TxStatus.asp';
609         my $allowmaestro   = charge_param('allowmaestro'); # Allow Maestro card transactions to be logged offline - you will need a MOTO a/c to convert to online without 3DS.
610 #::logDebug("SP".__LINE__.": apply3ds=$apply3ds; avscv2=$applyAVSCV2; txtype=$txtype. $::Values->{transtype}, $tx");
611                 
612                 $::Scratch->{tds} = '';
613                 $::Scratch->{mstatus} = '';
614                 $::Values->{amount} = '';
615                 $::Values->{transtype} = '';
616                 $::Values->{inv_no} = '';
617                 $::Values->{apply3ds} = '';
618                 $::Values->{applyavsvc2} = '';
619                 $::Values->{account_type} = '';
620                 my $billingState;
621                 my $deliveryState;
622         
623         my %result;
624         my %query;
625         
626         my (%actual) = map_actual();
627                 $actual  = \%actual;
628                 $opt     = {};
629         
630                 $vendor   = $opt->{id} || charge_param('id') || $::Variable->{MV_PAYMENT_ID};
631                 $opt->{host} = charge_param('host') || $::Variable->{MV_PAYMENT_HOST} || 'live.sagepay.com';
632                 $sagepayrequest = $opt->{sagepayrequest} = charge_param('sagepayrequest') || 'post';
633                 $opt->{use_wget} = charge_param('use_wget') || '1';
634                 $opt->{port}   = '443';
635 #::logDebug("SP".__LINE__.": host=$opt->{host}; spreq=$sagepayrequest");
636         
637         if ($txtype =~ /DEFERRED|PAYMENT|AUTHENTICATE|DEFAUTH/i) {
638                 $opt->{script} = '/gateway/service/vspdirect-register.vsp';
639                                 }
640         elsif ($txtype =~ /RELEASE/i) {
641                 $opt->{script} = '/gateway/service/release.vsp';
642                                 }
643         elsif ($txtype =~ /DIRECTREFUND/i) {
644                 $opt->{script} = '/gateway/service/directrefund.vsp';
645                 }
646         elsif ($txtype =~ /REFUND/i) {
647                 $opt->{script} = '/gateway/service/refund.vsp';
648                 }
649         elsif ($txtype =~ /VOID/i) {
650                 $opt->{script} = '/gateway/service/void.vsp';
651                 }
652         elsif ($txtype =~ /CANCEL/i) {
653                 $opt->{script} = '/gateway/service/cancel.vsp';
654                 }
655         elsif ($txtype =~ /ABORT/i) {
656                 $opt->{script} = '/gateway/service/abort.vsp';
657                 }
658         elsif ($txtype =~ /MANUAL/i) {
659                 $opt->{script} = '/gateway/service/manualpayment.vsp';
660                 }
661         elsif ($txtype =~ /REPEAT|REPEATDEFERRED/i) {
662                 $opt->{script} = '/gateway/service/repeat.vsp';
663                 }
664         elsif ($txtype =~ /AUTHORISE/i) {
665                 $opt->{script} = '/gateway/service/authorise.vsp';
666                 }
667         
668         
669                 my @override = qw/
670                                                         order_id
671                                                         mv_credit_card_exp_month
672                                                         mv_credit_card_exp_year
673                                                         mv_credit_card_start_month
674                                                         mv_credit_card_start_year
675                                                         mv_credit_card_issue_number
676                                                         mv_credit_card_number
677                                                 /;
678                 for(@override) {
679                         next unless defined $opt->{$_};
680                         $actual->{$_} = $opt->{$_};
681                 }
682         
683 #::logDebug("SP".__LINE__." actual map result: " . ::uneval($actual));
684         
685         my $pan = $actual->{mv_credit_card_number} unless defined $pan;
686            $pan =~ s/\D//g;
687            $actual->{mv_credit_card_exp_month}    =~ s/\D//g;
688            $actual->{mv_credit_card_exp_year}     =~ s/\D//g;
689            $actual->{mv_credit_card_exp_year}     =~ s/\d\d(\d\d)/$1/;
690         
691         my $exp  = sprintf '%02d%02d',
692                         $actual->{mv_credit_card_exp_month}, $actual->{mv_credit_card_exp_year};
693         
694         my $expshow = $exp;
695            $expshow =~ s/(\d\d)(\d\d)/$1\/$2/;
696         
697         my $cardType  = $actual->{mv_credit_card_type} || $CGI->{mv_credit_card_type} || $::Values->{mv_credit_card_type} unless defined $cardType;
698            $cardType = 'MC' if ($cardType =~ /mastercard/i);
699         
700         my $mvccStartMonth = $actual->{mv_credit_card_start_month} || $::Values->{mv_credit_card_start_month} || $::Values->{start_date_month};
701            $mvccStartMonth =~ s/\D//g;
702         
703         my $mvccStartYear = $actual->{mv_credit_card_start_year} || $::Values->{mv_credit_card_start_year} || $::Values->{start_date_year};
704            $mvccStartYear =~ s/\D//g;
705            $mvccStartYear =~ s/\d\d(\d\d)/$1/;
706         
707         my $mvccStartDate;
708                 if ($mvccStartMonth == '') { $mvccStartDate = '';
709                         }
710                 else { $mvccStartDate = sprintf '%02d%02d', $mvccStartMonth, $mvccStartYear;
711                 }
712         
713         my $issue = $actual->{mv_credit_card_issue_number} || $::Values->{mv_credit_card_issue_number} ||  $::Values->{card_issue_number};
714            $issue =~ s/\D//g;
715         
716         my $cvv2  =  $actual->{mv_credit_card_cvv2} || $::Values->{cvv2};
717            $cvv2  =~ s/\D//g;
718         
719         # override the configured AVSCV2/3DS settings when using a terminal
720            $applyAVSCV2 = '2' unless ($txtype =~ /PAYMENT|DEFERRED|AUTHORISE/i);
721            $apply3ds = '2' unless ($txtype =~ /PAYMENT|DEFERRED|AUTHENTICATE/i);
722         
723 # State must be only 2 letters, and is only required for the US. Other required fields default to a space to keep Sagepay happy.
724 # Filtering is now strict as Sagepay are making arbitrary changes to what they deem acceptable. 
725         my $cardHolder         = "$actual->{b_fname} $actual->{b_lname}" || "$actual->{fname} $actual->{lname}";
726            $cardHolder         =~ s/[^a-zA-Z0-9,. ]//gi;
727         my $billingSurname     = $actual->{b_lname} || $actual->{lname};
728            $billingSurname     =~ s/[^a-zA-Z0-9,. ]//gi;
729         my $billingFirstnames  = $actual->{b_fname} || $actual->{fname};
730            $billingFirstnames  =~ s/[^a-zA-Z0-9,. ]//gi;
731         my $billingCountry     = $actual->{b_country} || $actual->{country} || 'GB';
732            $billingCountry     = 'GB' if ($billingCountry =~ /UK/);
733         my $billingAddress1    = $actual->{b_address} || $actual->{address} || ' ';
734            $billingAddress1    =~ s/\// /g;
735            $billingAddress1    =~ s/[^a-zA-Z0-9,. ]//gi; 
736         my $billingAddress2    = $actual->{b_address2};
737            $billingAddress2    =~ s/[^a-zA-Z0-9,. ]//gi;
738         my $billingPostCode    = $actual->{b_zip} || $actual->{zip} || ' ';
739            $billingPostCode    =~ s/[^a-zA-Z0-9 ]//gi;
740            $billingState       = $actual->{b_state} || $actual->{state} || '';
741         my $billingCity        = $actual->{b_city} || $actual->{city} || ' ';
742            $billingCity       .= ", $billingState" unless $billingCountry =~ /US/i;
743            $billingCity        =~ s/[^a-zA-Z0-9,. ]//gi;
744                         undef $billingState unless  $billingCountry =~ /US/i;
745         my $billingPhone           = $actual->{b_phone} || $actual->{phone_day} || $actual->{phone_night};
746            $billingPhone       =~ s/[\(\)]/ /g;
747            $billingPhone       =~ s/[^0-9-+ ]//g;
748         
749         my $deliverySurname    = $actual->{lname};
750            $deliverySurname    =~ s/[^a-zA-Z0-9,. ]//gi;
751         my $deliveryFirstnames = $actual->{fname};
752            $deliveryFirstnames =~ s/[^a-zA-Z0-9,. ]//gi;
753         my $deliveryCountry    = $actual->{country} || 'GB';
754            $deliveryCountry    = 'GB' if ($deliveryCountry =~ /UK/);
755         my $deliveryPostCode   = $actual->{zip} || ' ';
756            $deliveryPostCode   =~ s/[^a-zA-Z0-9 ]//gi;
757         my $deliveryAddress1   = $actual->{address} || ' ';
758            $deliveryAddress1   =~ s/\// /gi;
759            $deliveryAddress1   =~ s/[^a-zA-Z0-9,. ]//gi;
760         my $deliveryAddress2   = $actual->{address2};
761            $deliveryAddress2   =~ s/[^a-zA-Z0-9,. ]//gi;
762            $deliveryState      = $actual->{state} || '';
763         my $deliveryCity       = $actual->{city} || ' ';
764            $deliveryCity      .= ", $deliveryState" unless $deliveryCountry =~ /US/i;
765            $deliveryCity       =~ s/[^a-zA-Z0-9,. ]//gi;
766                         undef $deliveryState unless $deliveryCountry =~ /US/i;
767         my $deliveryPhone      = $actual->{phone_day} || $actual->{phone_night};
768            $deliveryPhone      =~ s/[\(\)]/ /g;
769            $deliveryPhone      =~ s/[^0-9-+ ]//g;
770         
771         my $customerEmail      = $actual->{email};
772            $customerEmail      =~ s/[^a-zA-Z0-9.\@\-_]//gi;
773         my $contactFax         = $::Values->{fax} || '';
774            $contactFax         =~ s/[\(\)]/ /g;
775            $contactFax         =~ s/[^0-9-+ ]//gi;
776         my $giftAidPayment     = $::Values->{giftaidpayment} || charge_param('giftaidpayment') || '0';
777         my $authCode           = $::Values->{authcode} || '';
778         my $clientIPAddress    = $CGI::remote_addr if $CGI::remote_addr;
779                         $::Values->{authcode} = '';
780         
781 ::logDebug("SP".__LINE__.": bCity=$billingCity; mvccType=$cardType; start=$mvccStartDate; issue=$issue;");
782                 
783 # ISO currency code sent to SagePay, from the page or fall back to config files.
784         my $currency = $::Values->{iso_currency_code} || $::Values->{currency_code} || $Vend::Cfg->{Locale}{iso_currency_code} ||
785                                                  charge_param('currency') || $::Variable->{MV_PAYMENT_CURRENCY} || 'GBP';
786         
787         my $psp_host = $opt->{host};
788         my $convertoffline = charge_param('convertoffline');
789
790 #--- make the initial request to SagePay and get back the values for the ACS ---------------------------
791 if ($sagepayrequest eq 'post') {
792         $::Session->{sagepay}{CardRef}   = $pan;
793         $::Session->{sagepay}{CardRef}   =~ s/^(\d\d).*(\d\d\d\d)$/$1****$2/;
794 # vendorTxCode generated here in 'post', and retrieved from session later
795  my $order_id  = $Tag->time({ body => "%Y%m%d%H%M%S" }); 
796         $order_id .= $::Session->{id};
797         if ($txtype   =~ /RELEASE|VOID|ABORT|CANCEL/i) {
798                 $::Session->{sagepay}{vendorTxCode} = $::Values->{OrigVendorTxCode}
799         }
800         else {
801                 $::Session->{sagepay}{vendorTxCode} = $order_id
802         }
803
804    %query   =    (
805                     TxType                      => $txtype,
806                     Vendor                      => $vendor,
807                     AccountType                 => $accountType,
808                     VPSProtocol                 => $vpsprotocol,
809                     Apply3DSecure               => $apply3ds
810                  );
811
812         if ($txtype =~ /RELEASE/i) {
813                                                 $query{ReleaseAmount}       = $amount;
814                 }
815         
816         if ($txtype =~ /REFUND|REPEAT|AUTHORISE/) {
817                                         $query{RelatedVPSTxID}      = $::Values->{RelatedVPSTxID};
818                                         $query{RelatedVendorTxCode} = $::Values->{RelatedVendorTxCode};
819                                         $query{RelatedSecurityKey}  = $::Values->{RelatedSecurityKey};
820                                         $query{Description}         = $description;
821                                         $query{Amount}              = $amount;
822                 }
823         
824         if ($txtype =~ /REFUND|REPEAT/) {
825                                         $query{RelatedTxAuthNo}     = $::Values->{RelatedTxAuthNo};
826                                         $query{Currency}            = $currency;
827                 }
828         
829         if ($txtype =~ /VOID|ABORT|CANCEL|RELEASE/i) {
830                                         $query{VPSTxId}             = $::Values->{OrigVPSTxID};
831                                         $query{SecurityKey}         = $::Values->{OrigSecurityKey};
832                 }
833         
834         if ($txtype =~ /VOID|ABORT|RELEASE/i) {
835                                         $query{TxAuthNo}            = $::Values->{OrigTxAuthNo};
836                 }
837         
838         if ($txtype =~ /DIRECTREFUND|DEFERRED|PAYMENT|AUTHENTICATE|MANUAL/i) {
839                                         $query{CardType}            = $cardType;
840                                         $query{CardNumber}          = $pan;
841                                         $query{CardHolder}          = $cardHolder;
842                                         $query{Description}         = $description;
843                                         $query{Amount}              = $amount;
844                                         $query{Currency}            = $currency;
845                                         $query{ExpiryDate}          = $exp;
846                 }
847         
848         if ($txtype =~ /PAYMENT|DEFERRED|AUTHENTICATE|MANUAL/i) {
849                                         $query{BillingFirstNames}   = $billingFirstnames;
850                                         $query{BillingSurname}      = $billingSurname;
851                                         $query{BillingAddress1}     = $billingAddress1;
852                                         $query{BillingAddress2}     = $billingAddress2 if $billingAddress2;
853                                         $query{BillingCity}         = $billingCity;
854                                         $query{BillingPostCode}     = $billingPostCode;
855                                         $query{BillingState}        = $billingState if $billingState;
856                                         $query{BillingCountry}      = $billingCountry;
857                                         $query{BillingPhone}        = $billingPhone;
858                                         $query{DeliveryFirstNames}  = $deliveryFirstnames;
859                                         $query{DeliverySurname}     = $deliverySurname;
860                                         $query{DeliveryAddress1}    = $deliveryAddress1;
861                                         $query{DeliveryAddress2}    = $deliveryAddress2 if $deliveryAddress2;
862                                         $query{DeliveryCity}        = $deliveryCity;
863                                         $query{DeliveryPostCode}    = $deliveryPostCode;
864                                         $query{DeliveryState}       = $deliveryState if $deliveryState;
865                                         $query{DeliveryCountry}     = $deliveryCountry;
866                                         $query{DeliveryPhone}       = $deliveryPhone;
867                                         $query{ContactFax}          = $contactFax;
868                                         $query{CustomerEmail}       = $customerEmail;
869                                         $query{GiftAidPayment}      = $giftAidPayment;
870                                         $query{ClientIPAddress}     = $clientIPAddress;
871                                         $query{CV2}                 = $cvv2;
872                 }
873         
874         if ($txtype =~ /PAYMENT|DEFERRED|AUTHORISE/i) {
875                                         $query{ApplyAVSCV2}         = $applyAVSCV2;
876                 }
877         
878                                         $query{AuthCode}            = $authCode if $authCode;
879                                         $query{StartDate}           = $mvccStartDate if $mvccStartDate;
880                                         $query{IssueNumber}         = $issue if $issue; 
881         
882         }
883
884 #--- return from ACS  ------------------------------------------------------------------------
885  elsif ($sagepayrequest eq '3dsreturn') {
886                 $::Values->{sagepayrequest} = '';
887         $opt->{script} = $tdscallback;
888         $result{PaRes} = $CGI->{'PaRes'};
889         $result{MD}    = $CGI->{'MD'};
890
891 #::logDebug("SP".__LINE__.": New PaRes=$result{PaRes}\nMD=$result{MD}");
892          
893          %query = (
894                     MD                => $result{MD},
895                     PaRes             => $result{PaRes}
896                   );
897   
898   }
899
900 #--- query status from admin panel ----------------------------------------------------
901  elsif ($sagepayrequest eq 'querystatus') {
902    my %statusquery = (
903                       Vendor          => $vendor,
904                        VendorTxCode    => charge_param('vendortxcode')
905                      );
906        
907                 $opt->{script} = $checkstatusurl;
908                 my $post       = post_data($opt, \%statusquery);
909                 my $response   = $post->{status_line};
910                 my $page       = $post->{result_page};
911
912 #::logDebug("SP".__LINE__.": query page = $page\n");
913         
914         for my $line (split /\r\n/, $page) {
915                 $result{Status}       = $1 if ($line =~ /^Status=(.*)/i); 
916                 $result{StatusDetail} = $1 if ($line =~ /StatusDetail=(.*)/i);
917                 $result{TxType}       = $1 if ($line =~ /TransactionType=(.*)/i);
918                 $result{Authorised}   = $1 if ($line =~ /Authorised=(.*)/i);
919                 $result{TxAuthCode}   = $1 if ($line =~ /VPSAuthCode of (\d+)/i);
920                 $result{VPSTxId}      = $1 if ($line =~ /VPSTxId=(.*)/i);
921                 $result{Amount}       = $1 if ($line =~ /Amount=(.*)/i);
922                 $result{Currency}     = $1 if ($line =~ /Currency=(.*)/i);
923                 $result{ReceivedDate} = $1 if ($line =~ /Received=(.*)/i);
924                 $result{BatchID}      = $1 if ($line =~ /BatchID=(.*)/i);
925                 $result{Settled}      = $1 if ($line =~ /Settled=(.*)/i);
926                 }
927  }
928
929 #--- common stuff again --------------------------------------------------------------
930
931 my ($post, $response, @page);
932
933 # Test for gateway availability, and if not available optionally go off-line and complete
934 # transaction for manual processing later. Also go off-line if amount is zero, so as to log the
935 # transaction and email a receipt for audit purposes (useful mainly for subscription billing).
936
937         my ($request, $in);
938
939 #::logDebug("SP".__LINE__.": available=$available, amount=$amount, order_route=$::Values->{mv_order_route}, logzero=$logzero");
940
941 if (($available =~ /y|1/i) and ($amount > 0) and ($::Values->{mv_order_route} !~ /ptipm_route/i)) {
942         my $CMD = '/usr/bin/wget -nv --spider -T9 -t1';
943                 open (IN, "$CMD https://$psp_host 2>&1 |") || die "Could not open pipe to wget: $!\n";
944                         $in = <IN>;
945                         chop($in);
946                 close(IN);
947  
948                 $in = 'test' if ($::CGI->{offline} eq 'yes'); # testing only, will force offline mode
949                 if ($in =~ /^200 OK$/)  { 
950                         $request = 'psp'; 
951                         }
952                 else { 
953                         $request = 'offline'; 
954                         }
955                 if (($cardType =~ /MAESTRO|SWITCH/i) and ($allowmaestro != '1')) {
956                         $request = 'psp'; # Maestro must go through 3D Secure unless merchant a/c type is set to MOTO in a terminal
957                         }
958         }
959         elsif (($::Values->{mv_order_route} =~ /ptipm_route/)  and ($amount > 0)) {
960         $request = 'psp';
961         }
962         elsif (($available !~ /y|1/i) and ($amount > 0)) {
963         $request = 'psp';
964         }
965         elsif (($amount == 0) and ($logzero =~ /y|1/i)) {
966         $request = 'log';
967 }
968          
969          $result{VendorTxCode} = $::Session->{sagepay}{vendorTxCode};
970 #::logDebug("SP".__LINE__.": in=$in; request=$request; cardtype=$cardType; vendorTxCode=$result{VendorTxCode}");
971
972 if ($request eq 'psp') {
973 # Run normal routine to SagePay
974
975                 $query{VendorTxCode} = $::Session->{sagepay}{vendorTxCode};
976
977 #::logDebug("SP".__LINE__.": now for keys in query");
978   my @query;
979         foreach my $key (sort keys(%query)) {
980         ::logDebug("Query to SagePay: \"$key=$query{$key}\""); # nicely readable version of the string sent
981         push @query, "$key=$query{$key}";
982         }
983   
984   my $string = join '&', @query; # replicates the string as actually sent: useful to quote this for debugging
985 #::logDebug("SP".__LINE__.": string to SagePay: $string");
986                 
987                 $post     = post_data($opt, \%query);
988                 $response = $post->{status_line};
989                 $page     = $post->{result_page};
990
991 ::logDebug("SP".__LINE__.": response page:\n-------------------------\n$page \n---------------------------\nend of SagePay results page\n\n");
992                 
993                 $result{TxType}         = $txtype;
994                 $result{Currency}       = $currency;
995                 $result{CardRef}        = $::Session->{sagepay}{CardRef};
996                 $result{CardType}       = $cardType;
997                 $result{ExpAll}         = $exp;
998                 $result{amount}         = $amount;
999                 $result{request}        = $request;
1000                 $result{IP}             = $clientIPAddress;
1001                 $result{CardType}       = 'Electron' if ($cardType eq 'UKE');
1002                 $result{CardInfo}       = "$result{CardType}, $result{CardRef}, $expshow";
1003         
1004 # Find results off returned page
1005         for my $line (split /\r\n/, $page) {
1006                 $result{VPSTxID}        = $1 if ($line =~ /VPSTxID=(.*)/i); 
1007             $result{Status}         = $1 if ($line =~ /^Status=(.*)/i); 
1008             $result{StatusDetail}   = $1 if ($line =~ /StatusDetail=(.*)/i);
1009             $result{TxAuthNo}       = $1 if ($line =~ /TxAuthNo=(.*)/i);
1010                 $result{SecurityKey}    = $1 if ($line =~ /SecurityKey=(.*)/i);
1011                 $result{AVSCV2}         = $1 if ($line =~ /AVSCV2=(.*)/i);
1012                 $result{VPSProtocol}    = $1 if ($line =~ /VPSProtocol=(.*)/i);
1013                 $result{AddressResult}  = $1 if ($line =~ /AddressResult=(.*)/i);
1014                 $result{PostCodeResult} = $1 if ($line =~ /PostCodeResult=(.*)/i);
1015                 $result{CV2Result}      = $1 if ($line =~ /CV2Result=(.*)/i);
1016         
1017 # and the 3DSecure results too
1018                 $result{SecureStatus}   = $1 if ($line =~ /SecureStatus=(.*)/i);
1019                 $result{MD}             = $1 if ($line =~ /MD=(.*)/i);
1020                 $result{ACSURL}         = $1 if ($line =~ /ACSURL=(.*)/i);
1021                 $result{PaReq}          = $1 if ($line =~ /PaReq=(.*)/i);
1022                 $result{CAVV}           = $1 if ($line =~ /CAVV=(.*)/i);
1023                 }
1024                 
1025                 $result{StatusDetail} =~ s/, vendor was .*/\./;
1026 #::logDebug("SP".__LINE__.": cardRef=$result{CardRef}; txtype=$txtype; status=$result{Status}; detail=$result{StatusDetail}; securestatus=$result{SecureStatus}");
1027
1028 if ($txtype =~ /PAYMENT|DEFERRED|MANUAL|AUTHENTICATE/i) {
1029   
1030   if ($result{Status} =~ /3DAUTH/i) {
1031 #::logDebug("SP".__LINE__.": started status=3DAUTH");
1032 ## Use scratch values below to populate form inside iframe. This gets the page from the bank and re-populates
1033 ## the iframe. This page is then replaced with the 'tdsreturn' page, which shows the error message
1034 ## if there is an error, otherwise it does not show at all but is silently replaced with the receipt.
1035
1036                 $::Scratch->{acsurl}  = $result{ACSURL};
1037                 $::Scratch->{pareq}   = $result{PaReq};
1038                 $::Scratch->{termurl} = $termurl;
1039                 $::Scratch->{md}      = $result{MD};
1040
1041          my $sagepayfinal = $Tag->area({ href => "ord/tdsfinal" });
1042 $Tag->tag({ op => 'header', body => <<EOB });
1043 Status: 302 moved
1044 Location: $sagepayfinal
1045 EOB
1046       }
1047
1048   elsif ($result{Status} =~ /OK|REGISTERED|AUTHENTICATED|ATTEMPTONLY/i) {
1049 #::logDebug("SP".__LINE__.": reStatus=$result{Status}; securestatus=$result{SecureStatus}"); 
1050           if ($result{SecureStatus} =~ /NOTAUTHED/i) {
1051                  $result{MStatus} = $result{'pop.status'} = 'failed';
1052                  $::Scratch->{mstatus} = 'failed';
1053                  $::Scratch->{tds} = '';
1054                                 }
1055           else {
1056                  $result{MStatus} = $result{'pop.status'} = 'success' unless $convertoffline;
1057                  $result{'order-id'} ||= $::Session->{sagepay}{vendorTxCode} unless $convertoffline;
1058                  $result{'Terminal'} = 'success' if $convertoffline;
1059                  $result{'TxType'} = uc($txtype);
1060                  $result{'Status'} = 'OK';
1061                  $::Scratch->{mstatus} = 'success';
1062                  $::Scratch->{order_id} = $result{'order-id'};
1063                  $::Values->{mv_payment} = "Real-time card $result{CardInfo}";
1064          $::Values->{psp} = charge_param('psp') || 'SagePay';
1065          $::CGI::values{mv_todo} = 'submit';
1066                 if ($result{SecureStatus} =~ /OK|ATTEMPTONLY/i) {
1067                         $::Scratch->{tds} = 'yes' ;
1068                         $Vend::Session->{payment_result} = \%result;
1069
1070 ::logDebug("SP".__LINE__.": secureStatus=$result{SecureStatus} so now to run routes; result hash=".::uneval(\%result));
1071
1072                         Vend::Dispatch::do_process();
1073                         }
1074                 }
1075 #::logDebug("SP".__LINE__.": 3ds=$apply3ds; resStatus=$result{Status}; resSecureStatus=$result{SecureStatus}, mStatus= $result{MStatus}, orderid=$result{'order-id'}; tds=$::Scratch->{tds}\n");
1076                 }
1077
1078   elsif ($result{Status} =~ /NOTAUTHED/i) {
1079                 $result{MStatus} = $result{'pop.status'} = 'failed';
1080                 $::Scratch->{MStatus} = 'failed';
1081         if ($result{StatusDetail} =~ /AVS/i) {
1082                 $::Session->{errors}{Address} =  "Data mismatch<br>Please ensure that your address matches the address on your credit-card statement, and that the card security number matches the code on the back of your card\n";
1083               }
1084         else {
1085                 $::Session->{errors}{Payment} = "$result{StatusDetail} $result{Status}";
1086             }
1087 #::logDebug("SP".__LINE__.": res status = $result{Status}; MSt: $result{MStatus} or $::Session->{payment_result}{MStatus}\n");
1088         return;
1089       }
1090
1091   elsif ($result{Status} =~ /\w+/) {
1092 #::logDebug("SP".__LINE__.": resStatus (other) $result{Status}");
1093                 $result{MStatus} = $result{'pop.status'} = 'failed';
1094                 $::Scratch->{MStatus} = 'failed';
1095                 $result{MErrMsg} = $result{StatusDetail};
1096         $::Session->{errors}{Payment} = "$result{Status} $result{StatusDetail} The address and/or security code entered do not match those on file.";
1097                 return;
1098           }
1099  
1100   elsif (!$result{Status}) {
1101 #::logDebug("SP".__LINE__.": status=$result{Status}");
1102   # If the status response is read as empty, the customer will try to repeat the order. This next
1103   # Will try the 'check status' function first, but if the problem is that Sagepay are timing out 
1104   # then this may also timeout. If 'check status' is positive the transaction will be processed as 
1105   # though this glitch hadn't happened, otherwise it will be processed as a failure, thus preventing any 
1106   # attempt to repeat the order.
1107                 
1108                 $logorder = '1';
1109                 # Also log SagePay details for discussions with them
1110                 $logsagepay = '1';
1111         
1112         my %statusquery = (
1113                       Vendor          => $vendor,
1114                       VendorTxCode    => $::Session->{sagepay}{vendorTxCode}
1115                       );
1116        $opt->{script} = $checkstatusurl;
1117     my $post     = post_data($opt, \%statusquery);
1118     my $response = $post->{status_line};
1119     my $page     = $post->{result_page};
1120
1121 #::logDebug("SP".__LINE__.": checkstatus=$checkstatus; page=$page");
1122
1123                 for my $line (split /\r\n/, $page) {
1124                 $result{VPSTxID}    = $1 if ($line =~ /VPSTxId=(.*)/i);
1125                 $result{Authorised} = $1 if ($line =~ /^Authorised=(.*)/i);
1126                 $result{TxAuthNo}   = $1 if ($line =~ /VPSAuthCode=(.*)/i);
1127                          }
1128 #::logDebug("SP".__LINE__.": checkstatus,result=$result{Authorised}; authcode=$result{VPSAuthCode}; vtxcode=$vendorTxCode"); 
1129
1130        unless ($result{Authorised} =~ /YES/i) {
1131 my $unknown = <<EOF;
1132 ATTENTION: our payment processor has met an unexpected problem and we do not know if payment has
1133 been taken or not. Please check back with $::Variable->{COMPANY} on $::Variable->{PHONE}, quoting this 
1134 important reference point:
1135 <p>
1136 VendorTxCode:  $::Session->{sagepay}{vendorTxCode}
1137 <p>
1138 We apologise on behalf of our payment processor for the inconvenience
1139 EOF
1140  
1141 #::logDebug("SP".__LINE__.": $unknown\nresAuth=$result{Authorised}");
1142          $result{MStatus} = $result{'pop.status'};
1143          $result{'order-id'} ||= $opt->{order_id};
1144          $result{'TxType'} = 'NULL';
1145          $result{'Status'} = 'UNKNOWN status - check with SagePay before dispatching goods';
1146          $::Session->{errors}{SagePay} = $unknown;
1147                         }
1148        elsif($result{Authorised} =~ /YES/i) {
1149 #::logDebug("SP".__LINE__.": SP response was empty, now TxStatus is $result{Authorised} so force to success");
1150          $result{MStatus} = $result{'pop.status'} = 'success';
1151                  $result{'order-id'} ||= $::Session->{sagepay}{vendorTxCode};
1152                  $result{'Status'} = 'OK';
1153                  $result{'MStatus'} = 'success';
1154                  $::Scratch->{mstatus} = 'success';
1155                  $result{'TxType'} = uc($txtype);
1156                  }
1157    }
1158 }
1159
1160 # These next can only be done through a virtual terminal
1161         elsif($txtype =~ /RELEASE|REFUND|REPEAT|AUTHORISE|DIRECTREFUND|VOID|ABORT/i ) {
1162         if ($result{Status} =~ /OK/i) {
1163                         $result{Terminal} = 'success';
1164                         $result{'TxType'} = uc($txtype);
1165                                 }
1166         else {
1167                         $result{MStatus} = $result{'pop.status'} = 'failed';
1168                         $result{MErrMsg} = "$result{Status} $result{StatusDetail}";
1169                                 }
1170                 }
1171         }
1172
1173 elsif ($request =~ /offline|log/i) {
1174 # end of PSP request, now for either OFFLINE or LOG
1175   # force result to 'success' so that transaction completes
1176                  $vendorTxCode = $::Session->{sagepay}{vendorTxCode};
1177                  $result{MStatus} = $result{'pop.status'} = 'success';
1178                  $result{'order-id'} ||= $::Session->{sagepay}{vendorTxCode};
1179                  $result{'Status'} = 'OK';
1180                  $result{CardRef}  = $::Session->{sagepay}{CardRef};
1181                  $result{CardType} = $cardType;
1182                  $::Scratch->{mstatus} = 'success';
1183                  $::Scratch->{order_id} = $result{'order-id'};
1184                  $result{'TxType'} = uc($request);
1185                  $::Values->{mv_payment} = "Processing card $result{CardInfo}";
1186          $CGI::values{mv_todo} = 'submit';
1187
1188 #::logDebug("SP".__LINE__.": request=$request; tds=$::Scratch->{tds}");
1189            
1190            }
1191
1192      undef $request;
1193      $::Values->{request} = '';
1194
1195         ::logDebug("SP".__LINE__.":result=".::uneval(\%result));
1196
1197 # Now extra logging for backup order and/or log of events
1198         if ($logorder =~ /y|1/) {
1199 #--- write the full basket and address to failsafe file 
1200            $Tag->logorder('sagepay') or ::logError("SagePay: custom UserTag 'logorder.tag' not found"); # custom usertag to log full basket plus delivery details
1201                 }
1202         
1203         if (($logorder =~ /y|1/) or ($logsagepay =~ /y|1/)) {
1204 # Do we need this log??? Some have a keen interest in it ...
1205         my $sagepayend = time;
1206         my $sagepaytime = $sagepayend - $sagepaystart;
1207            chomp($sagepaytime);
1208 system("touch logs/sagepay.log");
1209 open (OUT, ">>logs/sagepay.log");
1210 printf OUT "
1211 Date of transaction = $sagepaydate
1212 Time to execute     = $sagepaytime seconds
1213 CardHolder          = $cardHolder
1214 Address             = $deliveryAddress1 $deliveryAddress2 $deliveryCity $deliveryState
1215 PostCode            = $deliveryPostCode
1216 Card reference      = $result{CardRef}
1217 Card type           = $result{CardType}
1218 Expiry              = $exp
1219 Amount              = $amount
1220 Currency            = $currency
1221 VendorTxCode        = $::Session->{sagepay}{vendorTxCode}
1222 SecurityKey         = $result{SecurityKey}
1223 VPSTxID             = $result{VPSTxID}
1224 TxAuthNo            = $result{TxAuthNo}
1225 TxType              = $result{TxType}
1226 Status              = $result{Status}
1227 AddressResult       = $result{AddressResult}
1228 PostCodeResult      = $result{PostCodeResult}
1229 CV2Result           = $result{CV2Result}
1230 SecureStatus        = $result{SecureStatus}
1231 StatusDetail        = $result{StatusDetail}
1232 IC result status    = $result{MStatus}
1233 ====================================================
1234 ";
1235 close(OUT);
1236
1237         }
1238                 
1239         undef $apply3ds;
1240         $::Values->{apply3ds} = '';
1241     
1242     return (%result);
1243   
1244 }
1245
1246 package Vend::Payment::SagePay;
1247
1248 1;
1249