Revert "Embed Safe 2.07 into Vend::Safe to avoid various problems with recent version...
[interchange.git] / lib / Vend / Payment / HSBC.pm
1 # Vend::Payment::HSBC - Interchange HSBC Payment module
2 #
3 # Copyright (C) 2013 Zolotek Resources Ltd
4 # All Rights Reserved.
5 #
6 # Author: Lyn St George <lyn@zolotek.net>
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public
19 # License along with this program; if not, write to the Free
20 # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21 # MA  02111-1307  USA.
22 #
23 package Vend::Payment::HSBC;
24
25 =head1 NAME
26
27 Vend::Payment::HSBC - Interchange HSBC Payments Module
28
29 =head1 PREREQUISITES
30
31     XML::Simple
32     URI
33     libwww-perl
34     Net::SSLeay
35         HTTP::Request
36         
37 Test for current installations, eg: perl -MXML::Simple -e 'print "It works\n"'
38 To install perl modules do: "emerge dev-perl/XML-Simple" or so on Gentoo, or on other systems do
39 "perl -MCPAN -e  'install XML::Simple'"
40
41 =head1 DESCRIPTION
42
43 The Vend::Payment::HSBC module implements the HSBC payment routine for use with Interchange.
44
45 #=========================
46
47 =head1 SYNOPSIS
48
49 Quick start:
50
51 Place this module in <ic_root>/lib/Vend/Payment, and call it in <ic_root>/interchange.cfg with
52 Require module Vend::Payment::HSBC. Ensure that your perl installation contains the modules
53 listed above and their pre-requisites.
54
55 Add a new payment route into catalog.cfg as follows:
56 Route hsbc gwhost https://www.secure-epayments.apixml.hsbc.com 
57 Route hsbc tdshost https://www.ccpa.hsbc.com/ccpa
58 Route hsbc returnurl http://__SERVER_NAME__/ord/hsbctdsreturn
59 Route hsbc username XXnnnnnn
60 Route hsbc currency GBP (default if not otherwise specified)
61 Route hsbc lcusername "XXX XXX" (currency codes, if they give you some usernames in lowercase and others in uppercase)
62 Route hsbc clientidGBP  nnnnn (5 digit number for that currency)
63 Route hsbc clientidEUR  nnnnn
64 Route hsbc clientidXXX  nnnnn
65 Route hsbc clientalias XXnnnnnnnn (ie country code plus your 8-digit merchant a/c id)
66 Route hsbc password xxx
67 Route hsbc txmode P (see below for others)
68 Route hsbc txtype Auth (PreAuth, Auth, PostAuth, Void, Credit, ForceInsertPreAuth,ForceInsertAuth, ReAuth, RePreAuth, ReviewPendingUpdate)
69 Route hsbc payment_type PaymentNoFraud (or Payment to use their fraud routines)
70 Route hsbc finalcheckoutpage ord/final (defaults to ord/checkout)
71 Route hsbc payment_type PaymentNoFraud (bypasses fraud checks, default is 'Payment' to run checks)
72
73 Route hsbc mail_txn_approved 1 (email you messages, a la Paypal, of payment made)
74 Route hsbc mail_txn_declined 1 (email you messages of payments declined, for monitoring fraud attempts) 
75 Route hsbc mail_txn_to (merchant's email address, defaults to EMAIL_SERVICE or ORDERS_TO)
76 Route hsbc hsbcrequest gwpost (to bypass 3DSecure, defaults to tdspost)
77
78 Add the following block of fraud screening rules, including comments as reminders. 
79 # Default fraud rules: mark '1' to accept the order conditional upon further processing, '2' to display the message to the 
80 # customer. Mark '0' to reject the order, or to not display a message. Eg '0 2' will reject with the message.
81 # '1 0' will accept and complete the order but not display a message, '1 2' will accept and display a message.
82 # All of these cases will be 'approved' by default but marked as 'review before capturing funds' by the bank.
83 Route hsbc fraud_4 '1 2' # The customer's billing address is in the UK but they 
84                                                   # are using an overseas issued card.
85 Route hsbc fraud_5 '1 2'  # The customer is using a card issued in a different 
86                                                   # country to the billing address.
87 Route hsbc fraud_6 '0 2'  # Failed AVS check.  Both the first line of the address 
88                                                   # and post code do not match the address held by the card issuer.
89 Route hsbc fraud_16 '0 2' # Failed AVS check.  Only the first line of the address matches 
90                                                   # the address held by the card issuer.
91 Route hsbc fraud_17 '1 2' # Failed AVS check.  Only the post code matches the address held                              
92                                                   # by the card issuer.
93 Route hsbc fraud_7 '1 0'  # The card issuer has not responded to the AVS request. 
94 Route hsbc fraud_8 '0 2'  # CVM does not match the card issuer's value.
95 Route hsbc fraud_9 '0 2'  # <BillToName> appears to be an invalid name. 
96 Route hsbc fraud_10 '0 2' # <BillToStreet1> appears to be an invalid address.
97 Route hsbc fraud_11 '0 2' # <BillToCity> appears to be an invalid address.
98 Route hsbc fraud_12 '0 0' # <BillToPostalCode> appears to be an invalid address. 
99 Route hsbc fraud_13 '1 0' # The AVS check cannot be performed on a British Forces Postal Office address.  
100 Route hsbc fraud_14 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
101                                                   # orders originating from this location.
102 Route hsbc fraud_15 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
103                                                   # orders originating from this location.
104 Route hsbc fraud_20 '0 0' # HSBC Merchant Services has recently observed a high incidence of 
105                                                   # fraudulent transactions using the name '<BillToName>'.
106 Route hsbc fraud_22 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
107                                                   # orders using this range of card numbers with a UK billing address. 
108 Route hsbc fraud_23 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
109                                                   # orders using this range of card numbers with Irish billing addresses.
110
111 If these rules are triggered, [scratch pspfraudmsg] will hold the message to display
112 to your customer, and should be included into the receipt, mail_receipt and report. In all
113 cases where a rule is triggered and payment needs manual review and acceptance an email will
114 be sent to the merchant with details.
115
116 TxMode: P = production, 
117                 Y = test 'yes' response 
118                 N = test, 'no' response
119                 R = test, random 'yes|no' response;
120                 FY = test, FraudShield 'yes' response
121                 FN = test, FraudShield 'no' response
122
123 Alter etc/log_transaction to wrap the following code around the "[charge route...]" call 
124 found in ln 172 (or nearby):
125         [if scratchd mstatus eq success]
126         [tmp name="charge_succeed"][scratch order_id][/tmp]
127         [else]
128         [tmp name="charge_succeed"][charge route="[var MV_PAYMENT_MODE]" amount="[scratch tmp_remaining]" order_id="[value mv_transaction_id]"][/tmp]
129         [/else]
130         [/if]
131 and change [var MV_PAYMENT_MODE] above to [value mv_payment_route] if you want to use Paypal or similar in conjunction with this
132
133 Also add this line just after '&final = yes' near the end of the credit_card section of etc/profiles.order:
134         &set=mv_payment_route hsbc if you change [var MV_PAYMENT_MODE] as above
135
136
137 If run from some sort of terminal this will also make refunds or send funds to a specified
138 credit card.
139
140
141
142 =head1 Changelog
143 v 1.0.1, February 2013, change of ownership from eSecurePayments to GlobalIris and
144   consequent change of URLs for gateway.
145 v 1.0.0, October 2011.
146   first public release
147
148 =head1 AUTHORS
149
150 Lyn St George <lyn@zolotek.net>
151
152 =cut
153
154 BEGIN {
155         eval {
156                 package Vend::Payment;
157                 require Net::SSLeay;
158                 require XML::Simple;
159                 require XML::Parser;
160                 require LWP;
161                 require MIME::Base64;
162                 require HTTP::Request::Common;
163                 import HTTP::Request::Common qw(POST);
164                 use HTTP::Request  ();
165                 use MIME::Base64;
166                 import Net::SSLeay qw(post_https make_form make_headers);
167         };
168
169                 $Vend::Payment::Have_Net_SSLeay = 1 unless $@;
170
171         if ($@) {
172                 $msg = __PACKAGE__ . ' requires XML::Simple, HTTP::Request, Net::SSLeay and LWP ' . $@;
173                 ::logGlobal ($msg);
174                 die $msg;
175         }
176
177         ::logGlobal("%s v1.0.1 20130222 payment module loaded",__PACKAGE__)
178                 unless $Vend::Quiet or ! $Global::VendRoot;
179 }
180
181 package Vend::Payment;
182
183 use strict;
184
185   my $gwhost;
186
187 sub hsbc {
188     my ($method, $response, $in, $opt, $actual, %result, $db, $dbh, $sth, $transactionid, $id, $md, $x);
189         my ($passoutdata,$telephone, $md, $cardref, $mailreview, $mailtxn, $display, $pass);
190         my (%actual) = map_actual();
191                 $actual  = \%actual;
192        $gwhost       = charge_param('gwhost') || 'https://apixml.globaliris.com';
193         my $tdshost      = charge_param('tdshost') || 'https://mpi.globaliris.com/ccpa'; # 'https://www.ccpa.hsbc.com/ccpa'; # only live ..
194         my $bypass3ds    = $::Values->{'bypass3ds'} || charge_param('bypass3ds') || '';
195         my $hsbcrequest  = $::Values->{'hsbcrequest'} || charge_param('hsbcrequest') || 'gwpost'; 
196                                            $::Values->{'hsbcrequest'} = '';
197         my $tdsreturn    = charge_param('tdsreturn') || '';
198            $hsbcrequest  = 'gwpost' if length $tdsreturn;
199         my $mailto       = charge_param('mail_txn_to') || $::Variable->{'EMAIL_SERVICE'} || $::Variable->{'ORDERS_TO'};
200         my $currency     = $::Values->{'iso_currency_code'} || $::Scratch->{'iso_currency_code'} || charge_param('currency') || 'GBP';
201                                            $::Values->{'iso_currency_code'} = '';
202         my $clientID     = 'clientid' . $currency;
203         my $clientid     = charge_param($clientID) or die "No client ID\n";
204         my $clientalias  = charge_param('clientalias') or warn "No client Alias\n";
205            $clientalias  .= $currency;
206         my $ccpaclientid = $clientalias . "01";
207         my $username     = charge_param('username') or die "No username id\n";
208            $username     = lc($username) if charge_param('lcusername') =~ /$currency/;
209         my $password     = charge_param('password') or die "No password\n";
210         my $txtype       = $::Values->{'txtype'} || charge_param('txtype') || 'Auth';
211                                            $::Values->{'txtype'} = '';
212         my $paymenttype  = $::Values->{'payment_type'} || charge_param('payment_type') || 'Payment'; # PaymentNoFraud bypasses HSBC's fraud tests
213                                            $::Values->{'payment_type'} = '';
214         my $txmode       = $::Values->{'txmode'} || charge_param('txmode') || 'P'; # P = production, others listed above
215                                            $::Values->{'txmode'} = '';
216         my $authcode     = $::Values->{'authcode'} || ''; # Obtained by voice authorisation for ForceInsertPreauth and the like
217         my $hsbctdspage  = charge_param('hsbctdspage') || 'ord/hsbctds'; 
218         my $returnurl    = charge_param('returnurl') || "$::Variable->{SECURE_SERVER}$::Variable->{CGI_URL}/ord/hsbctdsreturn";
219         my $finalcheckout = charge_param('finalcheckoutpage') || 'ord/checkout';
220         my $currencyshort = $currency;
221            $currencyshort =~ /(\w\w)/i;
222
223 #::logDebug("HSBC".__LINE__.": req=$hsbcrequest; type=$txtype; mode=$txmode; clientID=$clientid; clientalias=$clientalias, currency=$currency, username=$username");    
224
225         my $amount =  charge_param('amount') || Vend::Interpolate::total_cost() || $::Values->{'amount'}; 
226            $amount =~ s/^\D*//g;
227            $amount =~ s/\s*//g;
228         my $purchaseamount = $amount;
229            $amount =~ s/,//g;
230            $amount =  sprintf '%.2f', $amount;
231            $amount =~ s/\.//g; # £10.00 becomes 1000 for HSBC; 'purchaseamount' is for display to customer at PAS
232 #::logDebug("HSBC".__LINE__.":cp-amnt: ".charge_param('amount')."; v-amnt: $::Values->{'amount'}; ic-total_cost=" . Vend::Interpolate::total_cost());      
233         my $usebill  = '';
234            $usebill  = '1' if ($::Values->{'mv_same_billing'} == '0' || length $::Values->{'b_lname'});
235         my $name     = $usebill ? "$::Values->{'b_fname'} $::Values->{'b_lname'}" || '' : "$::Values->{'fname'} $::Values->{'lname'}" || '';
236         my $address1 = $usebill ? $::Values->{'b_address1'} : $::Values->{'address1'};
237         my $address2 = $usebill ? $::Values->{'b_address2'} : $::Values->{'address2'};
238         my $address3 = $usebill ? $::Values->{'b_address3'} : $::Values->{'address3'};
239         my $phone    = $::Values->{'phone_day'} || $::Values->{'phone_night'};
240         my $address  = "$address1, $address2, $address3";
241            $address  =~ s/,\s+$//g;
242            $address  =~ s/[^a-zA-Z0-9,.\- ]//gi;
243         my $city     = $usebill ? $::Values->{'b_city'} : $::Values->{'city'};
244            $city     =~ s/[^a-zA-Z0-9,.\- ]//gi;
245         my $state    = $usebill ? $::Values->{'b_state'} : $::Values->{'state'};
246            $state    =~ s/[^a-zA-Z0-9,.\- ]//gi;
247         my $zip      = $usebill ? $::Values->{'b_zip'} : $::Values->{'zip'};
248            $zip      =~ s/[^a-zA-Z0-9,.\- ]//gi;
249         my $country  = $usebill ? $::Values->{'b_country'} : $::Values->{'country'};
250            $country  = 'GB' if ($country eq 'UK');
251         my $email    = $actual->{'email'};
252            $email    =~ s/[^a-zA-Z0-9.\@\-_]//gi;
253         my $phone    = $actual->{'phone_day'} || $actual->{'phone_night'};
254            $phone    =~ s/[\(\)]/ /g;
255            $phone    =~ s/[^0-9-+ ]//g;
256         my $ipaddress  = $CGI::remote_addr if $CGI::remote_addr;
257            $ipaddress  =~ /(\d*)\.(\d*)\.(\d*)\.(\d*)/;
258         my $t1 = sprintf '%03d', $1;
259         my $t2 = sprintf '%03d', $2;
260         my $t3 = sprintf '%03d', $3;
261         my $t4 = sprintf '%03d', $4;
262            $ipaddress = "$t1.$t2.$t3.$t4" if $CGI::remote_addr;
263
264         my $pan = $actual->{'mv_credit_card_number'};
265            $pan =~ s/\D//g;
266            $actual->{'mv_credit_card_exp_month'}    =~ s/\D//g;
267            $actual->{'mv_credit_card_exp_year'}     =~ s/\D//g;
268            $actual->{'mv_credit_card_exp_year'}     =~ s/\d\d(\d\d)/$1/;
269
270            if (length $pan) {
271                   $cardref  = $pan;
272                   $cardref =~ s/^(\d\d).*(\d\d\d\d)$/$1**$2/;
273                   $::Session->{'CardRef'} = $cardref;
274                 }
275 #::logDebug("HSBC".__LINE__.": cardref=$cardref; $::Session->{CardRef}");
276
277         my $fname        = $::Values->{'fname'} || $actual->{'b_fname'} || $actual->{'fname'};
278            $fname                =~ s/[^a-zA-Z0-9,.\- ]//gi;
279         my $lname        = $::Values->{'lname'} || $actual->{'b_lname'} || $actual->{'lname'};
280            $lname                =~ s/[^a-zA-Z0-9,.\- ]//gi;
281         my $cardholder   = $::Values->{'cardholdername'} || "$fname $lname";
282            $cardholder   =~ s/[^a-zA-Z0-9,.\- ]//gi;
283 #::logDebug("HSBC".__LINE__.": name=$cardholder, $::Values->{cardholdername}; lname=$lname, $::Values->{lname}");
284         my $mvccexpmonth  = sprintf '%02d', $actual->{'mv_credit_card_exp_month'};
285         my $mvccexpyear   = sprintf '%02d', $actual->{'mv_credit_card_exp_year'};
286
287         my $cardexpiration = $mvccexpmonth . "/" . $mvccexpyear;
288         my $cardexpirationtds = $mvccexpyear . $mvccexpmonth;
289
290         my $mvccstartmonth = sprintf '%02d', $actual->{'mv_credit_card_start_month'} || $::Values->{'mv_credit_card_start_month'} || $::Values->{'start_date_month'};
291            $mvccstartmonth =~ s/\D//g;
292         
293         my $mvccstartyear = $actual->{'mv_credit_card_start_year'} || $::Values->{'mv_credit_card_start_year'} || $::Values->{'start_date_year'};
294            $mvccstartyear =~ s/\D//g;
295            $mvccstartyear =~ s/\d\d(\d\d)/$1/;
296            
297         my $startdate = $mvccstartmonth . "/" . $mvccstartyear;
298            $startdate = '' unless $mvccstartmonth > '0';
299
300         my $issuenumber = $actual->{'mv_credit_card_issue_number'} || $::Values->{'mv_credit_card_issue_number'} ||  $::Values->{'card_issue_number'};
301            $issuenumber =~ s/\D//g;
302         
303         my $cv2  =  $actual->{'mv_credit_card_cvv2'} || $::Values->{'cvv2'};
304            $cv2  =~ s/\D//g;
305            
306         my $cv2indicator = $::Values->{'cv2indicator'} || '2'; # 1=cv2 present, 2=cv2 customer claims not present on card, 5=intentionally not entered
307            $cv2indicator = '1' if length $cv2;
308            $::Session->{'cv2indicator'} = $cv2indicator if length $cv2;
309 #::logDebug("HSBC".__LINE__.": amt=$amount; currency=$currency; pan=$pan; cardholder=$cardholder; expm=$mvccexpmonth; expy=$mvccexpyear; issue=$issuenumber; cv2=$cv2; cv2indi=$cv2indicator; address=$address");
310
311
312 # Lookup unlisted iso codes from country.txt - major country and currency codes identical
313         my ($iso_country_code_numeric, $iso_currency_code_numeric, $iso_currency_symbol, $currencyexponent); 
314
315         if ($currency =~ /GBP/i) {
316                 $iso_currency_code_numeric = '826';
317                 $iso_currency_symbol = '&pound;';
318                 $currencyexponent = '2';
319                 }
320         elsif ($currency =~ /EUR/i) {
321                 $iso_currency_code_numeric = '978';
322                 $iso_currency_symbol = '&euro;';
323                 $currencyexponent = '2';
324                 }
325         elsif ($currency =~ /USD/i) {
326                 $iso_currency_code_numeric = '840';
327                 $iso_currency_symbol = '$';
328                 $currencyexponent = '2';
329                 }
330         elsif ($currency =~ /AUD/i) {
331                 $iso_currency_code_numeric = '036';
332                 $iso_currency_symbol = '$';
333                 $currencyexponent = '2';
334                 }
335         elsif ($currency =~ /CAD/i) {
336                 $iso_currency_code_numeric = '124';
337                 $iso_currency_symbol = '$';
338                 $currencyexponent = '2';
339                 }
340         elsif ($currency =~ /DKK/i) { 
341                 $iso_currency_code_numeric = '206';
342                 $iso_currency_symbol = 'kr';
343                 $currencyexponent = '2';
344                 }
345         elsif ($currency =~ /HKD/i) {
346                 $iso_currency_code_numeric = '344';
347                 $iso_currency_symbol = '$';
348                 $currencyexponent = '2';
349                 }
350         elsif ($currency =~ /JPY/i) {
351                 $iso_currency_code_numeric = '392';
352                 $iso_currency_symbol = 'Y';
353                 $currencyexponent = '0';
354                 }
355         elsif ($currency =~ /NZD/i) {
356                 $iso_currency_code_numeric = '554';
357                 $iso_currency_symbol = '$';
358                 $currencyexponent = '2';
359                 }
360         elsif ($currency =~ /NOK/i) { 
361                 $iso_currency_code_numeric = '578';
362                 $iso_currency_symbol = 'kr';
363                 $currencyexponent = '2';
364                 }
365         elsif ($currency =~ /SGD/i) { 
366                 $iso_currency_code_numeric = '702';
367                 $iso_currency_symbol = '$';
368                 $currencyexponent = '2';
369                 }
370         elsif ($currency =~ /SEK/i) { 
371                 $iso_currency_code_numeric = '752';
372                 $iso_currency_symbol = 'kr';
373                 $currencyexponent = '2';
374                 }
375         elsif ($currency =~ /CHF/i) {
376                 $iso_currency_code_numeric = '756';
377                 $iso_currency_symbol = 'CHF';
378                 $currencyexponent = '2';
379                 }
380
381         if ($country =~ /GB|UK/i) {
382                 $iso_country_code_numeric = '826';
383                 }
384         elsif ($country =~ /US/i) {
385                 $iso_country_code_numeric = '840';
386                 }
387         else {
388             $db  = dbref('country') || die ::errmsg("cannot open country table");
389             $dbh = $db->dbh() || die ::errmsg("cannot get handle for tbl 'country'");
390             $sth = $dbh->prepare("SELECT isonum FROM country WHERE code = '$country'");
391                 $sth->execute();
392                 $iso_country_code_numeric = $sth->fetchrow();           
393         };
394
395 #::logDebug("HSBC".__LINE__.": country=$country; currency=$currency; currencycode=$iso_currency_code_numeric; countrycode=$iso_country_code_numeric");
396                 $iso_currency_code_numeric = '826' unless defined $iso_currency_code_numeric;
397                 $iso_country_code_numeric  = '826' unless defined $iso_country_code_numeric;
398 #::logDebug("HSBC".__LINE__.": cardref=$cardref; req=$hsbcrequest;  country=$country; currency=$currency; currencycode=$iso_currency_code_numeric");
399 #::logDebug("HSBC".__LINE__.": usebill=$usebill; country: $::Values->{b_country}, $actual->{b_country}; $::Values->{country}, $actual->{country}");
400
401 #------------------------------------------------------------------------------------------------
402 # Go to PaymentAuthenticationServer first, before posting to bank, if doing 3DSecure
403 #
404         if ($hsbcrequest eq 'tdspost') {
405 #::logDebug("HSBC".__LINE__." started 3DS post to PAS $tdshost");
406 # now create encoded string to have returned with data for next post to gateway
407         $md = encode_base64("$pan:$cv2:$cv2indicator:$cardexpiration:$issuenumber:$amount:$x");
408
409                 $::Session->{'tdshost'} = $::Scratch->{'tdshost'}  = $tdshost;
410                 $::Scratch->{'cardexpiration'}   = $cardexpirationtds;
411                 $::Scratch->{'cardholderpan'} = $pan;
412                 $::Scratch->{'ccpaclientid'}  = $ccpaclientid;
413                 $::Scratch->{'currencyexponent'} = $currencyexponent;
414                 $::Scratch->{'md'} = $md;
415                 $::Scratch->{'purchaseamount'}  = $iso_currency_symbol . $purchaseamount;
416                 $::Scratch->{'purchaseamountraw'}  = $amount;
417                 $::Scratch->{'purchasecurrency'}  = $iso_currency_code_numeric;
418                 $::Scratch->{'resulturl'}  = $returnurl;
419                 
420                 $::Scratch->{'issuenumber'} = $issuenumber;
421
422         $::Scratch->{'tdsreturned'} = $::Scratch->{'tdsbounced'} = '';
423         
424         $::Scratch->{'tdsrun'} = $::Scratch->{'tdspause'} = '1';
425 #::logDebug("HSBC".__LINE__.": cursymbol=$iso_currency_symbol; pamnt=$purchaseamount; curcode=$iso_currency_code_numeric");     
426         my $hsbctds = $Tag->area({ href => "$hsbctdspage" });
427 #::logDebug("HSBC".__LINE__." tdshost=$::Scratch->{'tdshost'}, $tdshost; returnurl=$returnurl; tdspage=$hsbctdspage; tds=$hsbctds");
428 $Tag->tag({ op => 'header', body => <<EOB });
429 Status: 302 moved
430 Location: $hsbctds
431 EOB
432
433          }
434
435 #----------------------------------------------------------------------------------------------
436 # Header for use in gwpost or other ops
437 #
438
439         my $header = <<EOX;
440 <?xml version="1.0" encoding="UTF-8" ?> 
441   <EngineDocList>
442     <DocVersion DataType="String">1.0</DocVersion>
443     <EngineDoc>
444       <ContentType DataType="String">OrderFormDoc</ContentType>
445       <User>
446         <ClientId DataType="S32">$clientid</ClientId>
447         <Name DataType="String">$username</Name>
448         <Password DataType="String">$password</Password>
449        </User>
450 EOX
451
452
453 #----------------------------------------------------------------------------------------------------------
454 # Returned from the PAS - read results from PAS and action accordingly
455 #
456
457         if ($hsbcrequest eq 'gwpost') {
458 #::logDebug("HSBC".__LINE__." started $hsbcrequest; tdsreturn=$tdsreturn");
459
460 # Result from PAS is posted as key=val pairs in URL. Run this only if doing 3DS
461         if ($tdsreturn) {
462           my $tdsdata = ::http()->{entity} ;
463           my @tdsresult = split(/&/, $$tdsdata);
464 #::logDebug("HSBC".__LINE__.": tdsresult=@tdsresult; back=$$tdsdata");  
465           foreach (@tdsresult) {
466                 my($key,$val) = split(/=/,$_);
467                 $result{$key} = $val;
468 #::logDebug("HSBC".__LINE__.": tdsresult: $key = $val");
469           }
470 #::logDebug("HSBC".__LINE__." result from PAS:" .::uneval(\%result));
471
472         undef $result{'MErrMsg'};
473
474 # If PAS 3DSecure result is OK then post to gateway
475 # Simply post directly to 'gwpost' and not 'tdspost' if you want to bypass 3DSecure
476 # Set various values according to the CcpaResultsCode
477         if ($result{'CcpaResultsCode'} =~ /5|6/) {
478 #::logDebug("HSBC".__LINE__.":  code=$result{'CcpaResultsCode'}");
479         $result{'MErrMsg'} = "Authentication failed, please try again or use a different card";
480         return(%result);
481         }
482         elsif ($result{'CcpaResultsCode'} == '10') {
483         $result{'MErrMsg'} = "Payment error, please try again or use a different method";
484         return(%result);
485         }
486
487     elsif ($result{'CcpaResultsCode'} == '0'){
488                 $result{'PayerSecurityLevel'} = '2';
489                 $result{'PayerAuthenticationCode'} = $result{'CAVV'};
490                 $result{'PayerTxnId'} = $result{'XID'};
491                 $result{'CardholderPresentCode'} = '13';
492         }
493         elsif ($result{'CcpaResultsCode'} == '1') {
494                 $result{'PayerSecurityLevel'} = '5';
495                 $result{'CardholderPresentCode'} = '13';
496         
497         }
498         elsif ($result{'CcpaResultsCode'} == '2') {
499                 $result{'PayerSecurityLevel'} = '1';
500                 $result{'CardholderPresentCode'} = '13';
501         }
502         elsif ($result{'CcpaResultsCode'} == '3') {
503                 $result{'PayerSecurityLevel'} = '6';
504                 $result{'PayerAuthenticationCode'} = $result{'CAVV'};
505                 $result{'PayerTxnId'} = $result{'XID'};
506                 $result{'CardholderPresentCode'} = '13';
507         }
508         elsif ($result{'CcpaResultsCode'} == '14') {
509                 $result{'CardholderPresentCode'} = '7';
510         }
511         else {
512                 $result{'PayerSecurityLevel'} = '4';
513         }
514 # end after PAS results parsed
515         $md = decode_base64($result{'MD'});
516         ($pan,$cv2,$cv2indicator,$cardexpiration,$issuenumber,$amount,$x) = split /:/, $md;
517
518 #::logDebug("HSBC".__LINE__." pan=$pan, cv2=$cv2, cv2ind=$cv2indicator, sid=$::Session->{id}; scpan=$::Scratch->{'cardholderpan'}");
519         $pan ||= $::Scratch->{'cardholderpan'};
520
521
522         } # if tdsreturn
523
524         undef %result unless length $tdsreturn;
525
526 #::logDebug("HSBC".__LINE__." result from PAS after massage:" .::uneval(\%result));
527
528         my $cardholderpresentcode = $result{'CardholderPresentCode'} || '7'; # 7 for initial setup without 3DS   $paymenttype = 'PaymentNoFraud' if $cardholderpresentcode =~ /8|10/;
529         my $txid = $Tag->time({ body => "%Y%m%d%H%M%S" }) . $::Session->{id}; 
530            $txid = $::Values->{'txid'} if ($txtype =~ /^PostAuth|Void|^RePreAuth|^ReAuth|Credit/i and !$pan);
531                            $::Values->{'txid'} = '';
532         my $grouptxid = "rp-" . $txid unless $::Values->{'grouptxid'}; # Pull from db on repeats, else generate for first instance of periodic billing
533                            $::Values->{'grouptxid'} = '';
534    #    $order_id .= $::Session->{id};
535             $::Session->{'order_id'} = $txid;
536                 $::Session->{'HSBCHost'} = '';
537                 $::Scratch->{'tdspause'} = '';
538 #::logDebug("HSBC".__LINE__.": pan=$pan, cv2=$cv2; exp=$cardexpiration, amnt=$amount; txid=$txid; rpordernumer=$::Values->{rpordernumber}; chpcode=$cardholderpresentcode");
539
540                 $cv2 ||= $::Scratch->{'cv2'};
541                 $issuenumber ||= $::Scratch->{'issuenumber'};
542 #::logDebug("HSBC".__LINE__.": amt=$amount; pan=$pan; cardholder=$cardholder; fname=$fname, lname=$lname, cv2=$cv2; cv2indi=$cv2indicator; address=$address");
543
544    my $xmlOut = $header;
545    
546           $xmlOut .= <<EOX;   
547       <Instructions>
548         <Pipeline DataType="String">$paymenttype</Pipeline>
549       </Instructions>
550       <OrderFormDoc>
551         <Id DataType="String">$txid</Id>
552         <Mode DataType="String">$txmode</Mode>
553 EOX
554
555            $xmlOut .= <<EOX unless $txtype =~ /void|credit|postauth/i;
556          <Consumer>
557                         <BillTo>
558                           <Location>
559                                 <TelVoice>$telephone</TelVoice>
560                                 <Email>$email</Email>
561                                 <Address>
562                                   <Title></Title>
563                                   <FirstName>$fname</FirstName>
564                                   <LastName>$lname</LastName>
565                                   <Name>$cardholder</Name>
566 EOX
567
568                 $xmlOut .= <<EOX if $paymenttype eq 'Payment';
569                                   <Street1>$address1</Street1>
570                                   <Street2>$address2</Street2>
571                                   <Street3>$address3</Street3>
572                                   <City>$city</City>
573                                   <StateProv>$state</StateProv>
574                                   <PostalCode>$zip</PostalCode>
575                                   <Country>$iso_country_code_numeric</Country>
576 EOX
577
578                 $xmlOut .= <<EOX unless $txtype =~ /void|credit|refund|postauth/i;
579                                 </Address>
580                           </Location>
581                         </BillTo>
582           <PaymentMech>
583             <Type DataType="String">CreditCard</Type>
584              <CreditCard>
585               <Number DataType="String">$pan</Number>
586               <Expires DataType="ExpirationDate" Locale="$iso_country_code_numeric">$cardexpiration</Expires>
587 EOX
588
589           $xmlOut .= <<EOX if $startdate;
590                           <StartDate DataType="StartDate" Locale="$iso_country_code_numeric">$startdate</StartDate>
591 EOX
592 # for 'credit' as payment to card, rather than 'credit' as refund to txid
593 #            <Type DataType="String">CreditCard</Type>
594           $xmlOut .= <<EOX if ($txtype =~ /credit/i and length $pan);
595                 <Consumer>
596           <PaymentMech>
597              <CreditCard>
598               <Number DataType="String">$pan</Number>
599               <Expires DataType="ExpirationDate" Locale="$iso_country_code_numeric">$cardexpiration</Expires>
600              </CreditCard>
601           </PaymentMech>
602                 </Consumer>
603 EOX
604
605           $xmlOut .= <<EOX unless $txtype =~ /void|credit|refund|postauth/i;
606                           <Cvv2Indicator>$cv2indicator</Cvv2Indicator>
607                           <Cvv2Val>$cv2</Cvv2Val>
608                           <IssueNum>$issuenumber</IssueNum>
609            </CreditCard>
610           </PaymentMech>
611         </Consumer>
612 EOX
613           $xmlOut .= <<EOX;
614         <Transaction>
615           <Type DataType="String">$txtype</Type>
616 EOX
617
618           $xmlOut .= <<EOX  unless $txtype =~ /void|credit|postauth/i;
619               <CardholderPresentCode>$cardholderpresentcode</CardholderPresentCode>
620 EOX
621
622        $xmlOut .= <<EOX if $result{'XID'};
623                   <PayerAuthenticationCode>$result{'CAVV'}</PayerAuthenticationCode>
624                   <PayerTxnId>$result{'XID'}</PayerTxnId>
625                   <PayerSecurityLevel>$result{'PayerSecurityLevel'}</PayerSecurityLevel>
626 EOX
627
628            $xmlOut .= <<EOX if $authcode;
629                   <AuthCode>$authcode</AuthCode>
630 EOX
631
632           
633            $xmlOut .= <<EOX unless $txtype =~ /void|postauth/i;
634           <CurrentTotals>
635             <Totals>
636               <Total DataType="Money" Currency="$iso_currency_code_numeric">$amount</Total>
637             </Totals>
638           </CurrentTotals>
639 EOX
640
641                 $xmlOut .= <<EOX;
642         </Transaction>
643 EOX
644
645          $xmlOut .= <<EOX;
646       </OrderFormDoc>
647     </EngineDoc>
648   </EngineDocList>        
649 EOX
650
651 #::logDebug("HSBC".__LINE__.": xmlOut=$xmlOut");
652         my $msg = postHSBC($xmlOut);
653 #::logDebug("HSBC".__LINE__.": msg returned=$msg");     
654         my $xml = new XML::Simple(Keyattr => 'EngineDocList');
655         my $data = $xml->XMLin("$msg");
656            $data = $data->{'EngineDoc'}{'OrderFormDoc'};
657 #::logDebug("HSBC".__LINE__.": xmlback=".::uneval($data));
658
659 #::logDebug("HSBC".__LINE__.": MErrMsg=$result{'MErrMsg'}");            
660 #::logGlobal("HSBC: msg returned=$msg") if $result{'CcErrCode'} != '1'; 
661            $result{'TxStatus'} = $data->{'Overview'}{'TransactionStatus'};
662            $result{'TxType'} = $txtype;
663            $result{'CardRef'} = $::Session->{'CardRef'};
664            $result{'Currency'} = $currency if length $currency;
665  
666            $result{'HSBCTxId'} = $data->{'Transaction'}{'Id'}{'content'};
667            $result{'SecurityIndicator'} = $data->{'Transaction'}{'SecurityIndicator'}{'content'};
668            $result{'AuthCode'} = $data->{'Transaction'}{'AuthCode'}{'content'};
669            $result{'Type'} = $data->{'Transaction'}{'Auth'}{'content'};
670            $result{'SendRPvsData'} = $data->{'Transaction'}{'SendPbAvsData'}{'content'};
671            $result{'ProcReturnMsg'} = $data->{'Transaction'}{'CardProcResp'}{'ProcReturnMsg'}{'content'};
672            $result{'ProcReturnCode'} = $data->{'Transaction'}{'CardProcResp'}{'ProcReturnCode'}{'content'};
673            $result{'Status'} = $data->{'Transaction'}{'CardProcResp'}{'Status'}{'content'};
674            $result{'CcErrCode'} = $data->{'Transaction'}{'CardProcResp'}{'CcErrCode'}{'content'};
675            $result{'CcReturnMsg'} = $data->{'Transaction'}{'CardProcResp'}{'CcReturnMsg'}{'content'};
676            $result{'ProcAvsRespCode'} = $data->{'Transaction'}{'CardProcResp'}{'ProcAvsRespCode'}{'content'};
677            $result{'AvsDisplay'} = $data->{'Transaction'}{'CardProcResp'}{'AvsDisplay'}{'content'};
678            $result{'CcReturnMsg'} = $data->{'Transaction'}{'CardProcResp'}{'CcReturnMsg'}{'content'};
679            $result{'CommercialCardType'} = $data->{'Transaction'}{'CardProcResp'}{'CommercialCardType'}{'content'};
680            $result{'Cvv2Resp'} = $data->{'Transaction'}{'CardProcResp'}{'Cvv2Resp'}{'content'};
681            $result{'FraudStatus'} = $data->{'Overview'}{'FraudStatus'}{'content'};
682            $result{'FraudResult'} = $data->{'FraudInfo'}{'FraudResult'}{'content'};
683            $result{'FraudOrderScore'} = $data->{'FraudInfo'}{'OrderScore'}{'content'};
684
685            $pass = '1' if $result{'CcErrCode'} == '1'; # no errors, full pass
686
687 # Multiple triggers of fraud rules: fail if one rule fails      
688           if ($pass != '1') {
689            if ($data->{'FraudInfo'}{'Alerts'} =~ /ARRAY/i) {
690                  for my $i (0 .. 3) {
691                   $result{'FraudRuleId'} = $data->{'FraudInfo'}{'Alerts'}[$i]{'RuleId'}{'content'};
692                   $result{'FraudMsg'} = $data->{'FraudInfo'}{'Alerts'}[$i]{'Message'}{'content'};
693                   $pass = '';
694                   $pass = '1' if charge_param('fraud_' . $result{'FraudRuleId'}) =~ /1/;
695                   $display .= " \"$result{'FraudMsg'}\" <br>" if charge_param('fraud_' . $result{'FraudRuleId'}) =~ /2/;
696                           last if $pass != '1';
697                                   }
698                                 }
699                  else {
700                   $result{'FraudRuleId'} = $data->{'FraudInfo'}{'Alerts'}{'RuleId'}{'content'};
701                   $result{'FraudMsg'} = $data->{'FraudInfo'}{'Alerts'}{'Message'}{'content'};
702                   $pass = '1' if charge_param('fraud_' . $result{'FraudRuleId'}) =~ /1/; # list of all rule IDs plus descriptions
703                   $display = " \"$result{'FraudMsg'}\" " if charge_param('fraud_' .$result{'FraudRuleId'}) =~ /2/; # display the message
704                   }
705                 }
706             
707            $result{'FraudTotalScore'} = $data->{'FraudInfo'}{'TotalScore'}{'content'};
708            $result{'FraudResultCode'} = $data->{'FraudInfo'}{'FraudResultCode'}{'content'};
709            $result{'CardholderPresentCode'} = $data->{'Transaction'}{'CardholderPresentCode'}{'content'};
710 #::logDebug("HSBC".__LINE__.": status=$result{'TxStatus'}; authcode=$result{'AuthCode'}; errorcode=$result{CcErrCode}");
711
712         my $hsbcdate = $Tag->time({ body => "%A %d %B %Y, %k:%M:%S, %Z" });
713         my $amountshow =  $amount/'100';
714
715 #
716 #-------------------------------------------------------------------------------------------------
717 # Now to complete things
718 #
719
720 #::logDebug("HSBC".__LINE__.": rule=$result{FraudRuleId}; ReturnMsg=$result{'CcReturnMsg'}");
721          $display =~ s/Select \'Accept\' to proceed with the order\.//gi;
722          $display =~ s/It is recommended that caution is exercised\.//gi;
723          $::Scratch->{'pspfraudmsg'} = '';
724 #::logDebug("HSBC".__LINE__.": ErrCode=$result{CcErrCode}; MStatus=$result{MStatus}; SecureStatus=$result{'SecureStatus'}");
725   if (($pass == '1') and ($hsbcrequest eq 'gwpost')) {
726          $result{'MStatus'} = 'success';
727                  $result{'pop.status'} = 'success';
728                  $::Scratch->{'mstatus'} = 'success';
729          $result{'order-id'} ||= $::Session->{'order_id'};
730                  $::Values->{'mv_payment'} = "Real-time card $::Session->{'CardInfo'}";
731                   if (length $tdsreturn) {
732                    undef $tdsreturn; 
733                    $::Values->{'psp'} = charge_param('psp') || 'HSBC';
734                    $::Session->{'payment_id'} = $result{'order-id'};
735                    $::Scratch->{'order_id'} = $result{'order-id'};
736                    $::CGI::values{'mv_todo'} = 'submit';
737                    $::Scratch->{'tdsrun'} = '1';
738 #::logDebug("HSBC".__LINE__.": result=".::uneval(\%result));
739                    $::Scratch->{'pspfraudmsg'} = "Our bank has flagged this transaction, " 
740                                           . $display . 
741                                           " and we are obligated to review this order prior to accepting payment
742                                                 and making despatch. Our apologies for any inconvenience." 
743                                                         if $result{'CcErrCode'} != '1';
744
745                                         $Vend::Session->{'payment_result'} = \%result;
746                                         Vend::Dispatch::do_process();
747
748                                         }
749                           }
750    else  {
751          $result{'MStatus'} = $result{'pop.status'} = $::Scratch->{'mstatus'} = 'fail';
752          $result{'order-id'} = $result{'pop.order-id'} = '';
753          $result{'MErrMsg'} = 'Order not taken, card problem. ' . $display; 
754 #::logDebug("HSBC".__LINE__.": mstatus=$result{'MStatus'}; MErrMsg=$result{'MErrMsg'}"); 
755         }
756
757         # my $review = $result{'OverView'}{'FraudStatus'} || $result{'OverView'}{'FraudStatus'} 
758 #::logDebug("HSBC".__LINE__.": fraudstatus=$result{'FraudStatus'}");
759                 if ($result{'FraudStatus'} =~ /Review/i) {
760                 $mailreview = <<EOM;
761 At $hsbcdate, HSBC TxID $result{HSBCTxId},  for 
762 $result{Currency} $amountshow has been marked both "$result{ProcReturnMsg}" and 
763 "$result{CcReturnMsg}" with ErrorCode "$result{CcErrCode}". 
764 EOM
765
766                 $::Tag->email({ to => $mailto, from => $mailto, reply => $mailto,
767                                                 subject => "HSBC txn: blocked for fraud review",
768                                                 body => "$mailreview\n\n",
769                                           });
770 #::logDebug("HSBC".__LINE__.": blocked, now to mail: $mailreview");
771                 };
772         
773                 if (($result{'FraudStatus'} =~ /Decline/i) and (charge_param('mail_txn_declined') == '1')) {
774                 $::Tag->email({ to => $mailto, from => $mailto, reply => $mailto,
775                                                 subject => "HSBC txn: declined",
776                                                 body => "$mailreview\n\n",
777                                           });
778 #::logDebug("HSBC".__LINE__.": declined, now to mail: $mailreview");
779                 };
780                 
781                 $mailtxn = "At $hsbcdate you received payment from $cardholder of
782 $result{Currency}$amountshow for OrderID $::Session->{'payment_id'}, HSBC txn ID $result{HSBCTxId},
783  AuthCode $result{AuthCode}, from IP $ipaddress with card $result{CardBrand} $result{CardRef}";
784
785                 if (($result{'CcReturnMsg'} =~ /Approved/i) and (charge_param('mail_txn_approved') == '1')) {
786                 $::Tag->email({ to => $mailto, from => $mailto, reply => $mailto,
787                                                 subject => "HSBC txn: approved",
788                                                 body => "$mailtxn\n\n",
789                                           });
790 #::logDebug("HSBC".__LINE__.": approved, now to mailto $mailto: $mailtxn");
791                 };
792
793 #::logDebug("HSBC".__LINE__.": result=".::uneval(\%result));
794                 return (%result);
795
796         } # if gwpost
797
798 }
799
800 #
801 #--------------------------------------------------------------------------------------------------
802 # End of main routine
803 #--------------------------------------------------------------------------------------------------
804 #
805
806 sub postHSBC {
807          my $self = shift;
808 #::logDebug("HSBC".__LINE__."\n###############\nxmlout=$self\n================\n");
809          my $ua = LWP::UserAgent->new;
810             $ua->timeout(30);
811          my $req = HTTP::Request->new('POST' => $gwhost);
812                 $req->content_type('text/xml');
813                 $req->content_length( length($self) );
814                 $req->content($self);
815          my $res = $ua->request($req);
816          my $respcode = $res->status_line;
817
818         if ($res->is_success && $res->content){
819                 return ($res->content());
820                           }
821         else {
822                 $::Session->{'errors'}{'HSBC'} = "No response from the HSBC payment gateway";
823                 return $res->status_line;
824      }
825 }
826
827 package Vend::Payment::HSBC;
828
829 1;