[interchange] Add HSBC payment module.

Stefan Hornburg interchange-cvs at icdevgroup.org
Thu May 30 18:23:16 UTC 2013


commit e6dbb16719c9d419969e4c95a28c965f4a458c4e
Author: Lyn St George <lyn at zolotek.net>
Date:   Thu May 30 20:22:54 2013 +0200

    Add HSBC payment module.

 MANIFEST                 |    1 +
 lib/Vend/Payment/HSBC.pm |  829 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 830 insertions(+), 0 deletions(-)
---
diff --git a/MANIFEST b/MANIFEST
index 631603e..4a98e7c 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -1147,6 +1147,7 @@ lib/Vend/Payment/EFSNet.pm
 lib/Vend/Payment/Ezic.pm
 lib/Vend/Payment/Getitcard.pm
 lib/Vend/Payment/GoogleCheckout.pm
+lib/Vend/Payment/HSBC.pm
 lib/Vend/Payment/ICS.pm
 lib/Vend/Payment/iTransact.pm
 lib/Vend/Payment/Linkpoint.pm
diff --git a/lib/Vend/Payment/HSBC.pm b/lib/Vend/Payment/HSBC.pm
new file mode 100644
index 0000000..34fb08a
--- /dev/null
+++ b/lib/Vend/Payment/HSBC.pm
@@ -0,0 +1,829 @@
+# Vend::Payment::HSBC - Interchange HSBC Payment module
+#
+# Copyright (C) 2013 Zolotek Resources Ltd
+# All Rights Reserved.
+#
+# Author: Lyn St George <lyn at zolotek.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free
+# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA  02111-1307  USA.
+#
+package Vend::Payment::HSBC;
+
+=head1 NAME
+
+Vend::Payment::HSBC - Interchange HSBC Payments Module
+
+=head1 PREREQUISITES
+
+    XML::Simple
+    URI
+    libwww-perl
+    Net::SSLeay
+	HTTP::Request
+	
+Test for current installations, eg: perl -MXML::Simple -e 'print "It works\n"'
+To install perl modules do: "emerge dev-perl/XML-Simple" or so on Gentoo, or on other systems do
+"perl -MCPAN -e  'install XML::Simple'"
+
+=head1 DESCRIPTION
+
+The Vend::Payment::HSBC module implements the HSBC payment routine for use with Interchange.
+
+#=========================
+
+=head1 SYNOPSIS
+
+Quick start:
+
+Place this module in <ic_root>/lib/Vend/Payment, and call it in <ic_root>/interchange.cfg with
+Require module Vend::Payment::HSBC. Ensure that your perl installation contains the modules
+listed above and their pre-requisites.
+
+Add a new payment route into catalog.cfg as follows:
+Route hsbc gwhost https://www.secure-epayments.apixml.hsbc.com 
+Route hsbc tdshost https://www.ccpa.hsbc.com/ccpa
+Route hsbc returnurl http://__SERVER_NAME__/ord/hsbctdsreturn
+Route hsbc username XXnnnnnn
+Route hsbc currency GBP (default if not otherwise specified)
+Route hsbc lcusername "XXX XXX" (currency codes, if they give you some usernames in lowercase and others in uppercase)
+Route hsbc clientidGBP	nnnnn (5 digit number for that currency)
+Route hsbc clientidEUR	nnnnn
+Route hsbc clientidXXX	nnnnn
+Route hsbc clientalias XXnnnnnnnn (ie country code plus your 8-digit merchant a/c id)
+Route hsbc password xxx
+Route hsbc txmode P (see below for others)
+Route hsbc txtype Auth (PreAuth, Auth, PostAuth, Void, Credit, ForceInsertPreAuth,ForceInsertAuth, ReAuth, RePreAuth, ReviewPendingUpdate)
+Route hsbc payment_type PaymentNoFraud (or Payment to use their fraud routines)
+Route hsbc finalcheckoutpage ord/final (defaults to ord/checkout)
+Route hsbc payment_type PaymentNoFraud (bypasses fraud checks, default is 'Payment' to run checks)
+
+Route hsbc mail_txn_approved 1 (email you messages, a la Paypal, of payment made)
+Route hsbc mail_txn_declined 1 (email you messages of payments declined, for monitoring fraud attempts) 
+Route hsbc mail_txn_to (merchant's email address, defaults to EMAIL_SERVICE or ORDERS_TO)
+Route hsbc hsbcrequest gwpost (to bypass 3DSecure, defaults to tdspost)
+
+Add the following block of fraud screening rules, including comments as reminders. 
+# Default fraud rules: mark '1' to accept the order conditional upon further processing, '2' to display the message to the 
+# customer. Mark '0' to reject the order, or to not display a message. Eg '0 2' will reject with the message.
+# '1 0' will accept and complete the order but not display a message, '1 2' will accept and display a message.
+# All of these cases will be 'approved' by default but marked as 'review before capturing funds' by the bank.
+Route hsbc fraud_4 '1 2' # The customer's billing address is in the UK but they 
+						  # are using an overseas issued card.
+Route hsbc fraud_5 '1 2'  # The customer is using a card issued in a different 
+						  # country to the billing address.
+Route hsbc fraud_6 '0 2'  # Failed AVS check.  Both the first line of the address 
+						  # and post code do not match the address held by the card issuer.
+Route hsbc fraud_16 '0 2' # Failed AVS check.  Only the first line of the address matches 
+						  # the address held by the card issuer.
+Route hsbc fraud_17 '1 2' # Failed AVS check.  Only the post code matches the address held 				
+						  # by the card issuer.
+Route hsbc fraud_7 '1 0'  # The card issuer has not responded to the AVS request. 
+Route hsbc fraud_8 '0 2'  # CVM does not match the card issuer's value.
+Route hsbc fraud_9 '0 2'  # <BillToName> appears to be an invalid name. 
+Route hsbc fraud_10 '0 2' # <BillToStreet1> appears to be an invalid address.
+Route hsbc fraud_11 '0 2' # <BillToCity> appears to be an invalid address.
+Route hsbc fraud_12 '0 0' # <BillToPostalCode> appears to be an invalid address. 
+Route hsbc fraud_13 '1 0' # The AVS check cannot be performed on a British Forces Postal Office address.  
+Route hsbc fraud_14 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
+						  # orders originating from this location.
+Route hsbc fraud_15 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
+						  # orders originating from this location.
+Route hsbc fraud_20 '0 0' # HSBC Merchant Services has recently observed a high incidence of 
+						  # fraudulent transactions using the name '<BillToName>'.
+Route hsbc fraud_22 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
+						  # orders using this range of card numbers with a UK billing address. 
+Route hsbc fraud_23 '0 0' # HSBC Merchant Services has recently identified a number of fraudulent 
+						  # orders using this range of card numbers with Irish billing addresses.
+
+If these rules are triggered, [scratch pspfraudmsg] will hold the message to display
+to your customer, and should be included into the receipt, mail_receipt and report. In all
+cases where a rule is triggered and payment needs manual review and acceptance an email will
+be sent to the merchant with details.
+
+TxMode: P = production, 
+		Y = test 'yes' response 
+		N = test, 'no' response
+		R = test, random 'yes|no' response;
+		FY = test, FraudShield 'yes' response
+		FN = test, FraudShield 'no' response
+
+Alter etc/log_transaction to wrap the following code around the "[charge route...]" call 
+found in ln 172 (or nearby):
+	[if scratchd mstatus eq success]
+	[tmp name="charge_succeed"][scratch order_id][/tmp]
+	[else]
+	[tmp name="charge_succeed"][charge route="[var MV_PAYMENT_MODE]" amount="[scratch tmp_remaining]" order_id="[value mv_transaction_id]"][/tmp]
+	[/else]
+	[/if]
+and change [var MV_PAYMENT_MODE] above to [value mv_payment_route] if you want to use Paypal or similar in conjunction with this
+
+Also add this line just after '&final = yes' near the end of the credit_card section of etc/profiles.order:
+	&set=mv_payment_route hsbc if you change [var MV_PAYMENT_MODE] as above
+
+
+If run from some sort of terminal this will also make refunds or send funds to a specified
+credit card.
+
+
+
+=head1 Changelog
+v 1.0.1, February 2013, change of ownership from eSecurePayments to GlobalIris and
+  consequent change of URLs for gateway.
+v 1.0.0, October 2011.
+  first public release
+
+=head1 AUTHORS
+
+Lyn St George <lyn at zolotek.net>
+
+=cut
+
+BEGIN {
+	eval {
+		package Vend::Payment;
+		require Net::SSLeay;
+		require XML::Simple;
+		require XML::Parser;
+		require LWP;
+		require MIME::Base64;
+		require HTTP::Request::Common;
+		import HTTP::Request::Common qw(POST);
+		use HTTP::Request  ();
+		use MIME::Base64;
+		import Net::SSLeay qw(post_https make_form make_headers);
+	};
+
+		$Vend::Payment::Have_Net_SSLeay = 1 unless $@;
+
+	if ($@) {
+		$msg = __PACKAGE__ . ' requires XML::Simple, HTTP::Request, Net::SSLeay and LWP ' . $@;
+		::logGlobal ($msg);
+		die $msg;
+	}
+
+	::logGlobal("%s v1.0.1 20130222 payment module loaded",__PACKAGE__)
+		unless $Vend::Quiet or ! $Global::VendRoot;
+}
+
+package Vend::Payment;
+
+use strict;
+
+  my $gwhost;
+
+sub hsbc {
+    my ($method, $response, $in, $opt, $actual, %result, $db, $dbh, $sth, $transactionid, $id, $md, $x);
+	my ($passoutdata,$telephone, $md, $cardref, $mailreview, $mailtxn, $display, $pass);
+	my (%actual) = map_actual();
+		$actual  = \%actual;
+       $gwhost       = charge_param('gwhost') || 'https://apixml.globaliris.com';
+	my $tdshost      = charge_param('tdshost') || 'https://mpi.globaliris.com/ccpa'; # 'https://www.ccpa.hsbc.com/ccpa'; # only live ..
+	my $bypass3ds    = $::Values->{'bypass3ds'} || charge_param('bypass3ds') || '';
+	my $hsbcrequest  = $::Values->{'hsbcrequest'} || charge_param('hsbcrequest') || 'gwpost'; 
+					   $::Values->{'hsbcrequest'} = '';
+	my $tdsreturn    = charge_param('tdsreturn') || '';
+	   $hsbcrequest  = 'gwpost' if length $tdsreturn;
+	my $mailto       = charge_param('mail_txn_to') || $::Variable->{'EMAIL_SERVICE'} || $::Variable->{'ORDERS_TO'};
+	my $currency     = $::Values->{'iso_currency_code'} || $::Scratch->{'iso_currency_code'} || charge_param('currency') || 'GBP';
+					   $::Values->{'iso_currency_code'} = '';
+	my $clientID     = 'clientid' . $currency;
+	my $clientid     = charge_param($clientID) or die "No client ID\n";
+	my $clientalias  = charge_param('clientalias') or warn "No client Alias\n";
+ 	   $clientalias  .= $currency;
+	my $ccpaclientid = $clientalias . "01";
+	my $username     = charge_param('username') or die "No username id\n";
+	   $username     = lc($username) if charge_param('lcusername') =~ /$currency/;
+	my $password     = charge_param('password') or die "No password\n";
+	my $txtype       = $::Values->{'txtype'} || charge_param('txtype') || 'Auth';
+					   $::Values->{'txtype'} = '';
+	my $paymenttype  = $::Values->{'payment_type'} || charge_param('payment_type') || 'Payment'; # PaymentNoFraud bypasses HSBC's fraud tests
+					   $::Values->{'payment_type'} = '';
+	my $txmode       = $::Values->{'txmode'} || charge_param('txmode') || 'P'; # P = production, others listed above
+					   $::Values->{'txmode'} = '';
+	my $authcode     = $::Values->{'authcode'} || ''; # Obtained by voice authorisation for ForceInsertPreauth and the like
+	my $hsbctdspage  = charge_param('hsbctdspage') || 'ord/hsbctds'; 
+	my $returnurl    = charge_param('returnurl') || "$::Variable->{SECURE_SERVER}$::Variable->{CGI_URL}/ord/hsbctdsreturn";
+	my $finalcheckout = charge_param('finalcheckoutpage') || 'ord/checkout';
+	my $currencyshort = $currency;
+	   $currencyshort =~ /(\w\w)/i;
+
+#::logDebug("HSBC".__LINE__.": req=$hsbcrequest; type=$txtype; mode=$txmode; clientID=$clientid; clientalias=$clientalias, currency=$currency, username=$username");	
+
+	my $amount =  charge_param('amount') || Vend::Interpolate::total_cost() || $::Values->{'amount'}; 
+	   $amount =~ s/^\D*//g;
+	   $amount =~ s/\s*//g;
+	my $purchaseamount = $amount;
+	   $amount =~ s/,//g;
+	   $amount =  sprintf '%.2f', $amount;
+	   $amount =~ s/\.//g; # £10.00 becomes 1000 for HSBC; 'purchaseamount' is for display to customer at PAS
+#::logDebug("HSBC".__LINE__.":cp-amnt: ".charge_param('amount')."; v-amnt: $::Values->{'amount'}; ic-total_cost=" . Vend::Interpolate::total_cost()); 	   
+	my $usebill  = '';
+	   $usebill  = '1' if ($::Values->{'mv_same_billing'} == '0' || length $::Values->{'b_lname'});
+	my $name     = $usebill ? "$::Values->{'b_fname'} $::Values->{'b_lname'}" || '' : "$::Values->{'fname'} $::Values->{'lname'}" || '';
+	my $address1 = $usebill ? $::Values->{'b_address1'} : $::Values->{'address1'};
+	my $address2 = $usebill ? $::Values->{'b_address2'} : $::Values->{'address2'};
+	my $address3 = $usebill ? $::Values->{'b_address3'} : $::Values->{'address3'};
+	my $phone    = $::Values->{'phone_day'} || $::Values->{'phone_night'};
+	my $address  = "$address1, $address2, $address3";
+	   $address  =~ s/,\s+$//g;
+	   $address  =~ s/[^a-zA-Z0-9,.\- ]//gi;
+	my $city     = $usebill ? $::Values->{'b_city'} : $::Values->{'city'};
+	   $city     =~ s/[^a-zA-Z0-9,.\- ]//gi;
+	my $state    = $usebill ? $::Values->{'b_state'} : $::Values->{'state'};
+	   $state    =~ s/[^a-zA-Z0-9,.\- ]//gi;
+	my $zip      = $usebill ? $::Values->{'b_zip'} : $::Values->{'zip'};
+	   $zip      =~ s/[^a-zA-Z0-9,.\- ]//gi;
+	my $country  = $usebill ? $::Values->{'b_country'} : $::Values->{'country'};
+	   $country  = 'GB' if ($country eq 'UK');
+	my $email    = $actual->{'email'};
+	   $email    =~ s/[^a-zA-Z0-9.\@\-_]//gi;
+	my $phone    = $actual->{'phone_day'} || $actual->{'phone_night'};
+	   $phone    =~ s/[\(\)]/ /g;
+	   $phone    =~ s/[^0-9-+ ]//g;
+	my $ipaddress  = $CGI::remote_addr if $CGI::remote_addr;
+	   $ipaddress  =~ /(\d*)\.(\d*)\.(\d*)\.(\d*)/;
+	my $t1 = sprintf '%03d', $1;
+	my $t2 = sprintf '%03d', $2;
+	my $t3 = sprintf '%03d', $3;
+	my $t4 = sprintf '%03d', $4;
+	   $ipaddress = "$t1.$t2.$t3.$t4" if $CGI::remote_addr;
+
+	my $pan = $actual->{'mv_credit_card_number'};
+ 	   $pan =~ s/\D//g;
+	   $actual->{'mv_credit_card_exp_month'}    =~ s/\D//g;
+	   $actual->{'mv_credit_card_exp_year'}     =~ s/\D//g;
+	   $actual->{'mv_credit_card_exp_year'}     =~ s/\d\d(\d\d)/$1/;
+
+	   if (length $pan) {
+		  $cardref  = $pan;
+		  $cardref =~ s/^(\d\d).*(\d\d\d\d)$/$1**$2/;
+		  $::Session->{'CardRef'} = $cardref;
+		}
+#::logDebug("HSBC".__LINE__.": cardref=$cardref; $::Session->{CardRef}");
+
+	my $fname        = $::Values->{'fname'} || $actual->{'b_fname'} || $actual->{'fname'};
+	   $fname 		 =~ s/[^a-zA-Z0-9,.\- ]//gi;
+	my $lname        = $::Values->{'lname'} || $actual->{'b_lname'} || $actual->{'lname'};
+	   $lname		 =~ s/[^a-zA-Z0-9,.\- ]//gi;
+	my $cardholder   = $::Values->{'cardholdername'} || "$fname $lname";
+	   $cardholder   =~ s/[^a-zA-Z0-9,.\- ]//gi;
+#::logDebug("HSBC".__LINE__.": name=$cardholder, $::Values->{cardholdername}; lname=$lname, $::Values->{lname}");
+	my $mvccexpmonth  = sprintf '%02d', $actual->{'mv_credit_card_exp_month'};
+	my $mvccexpyear   = sprintf '%02d', $actual->{'mv_credit_card_exp_year'};
+
+	my $cardexpiration = $mvccexpmonth . "/" . $mvccexpyear;
+	my $cardexpirationtds = $mvccexpyear . $mvccexpmonth;
+
+	my $mvccstartmonth = sprintf '%02d', $actual->{'mv_credit_card_start_month'} || $::Values->{'mv_credit_card_start_month'} || $::Values->{'start_date_month'};
+	   $mvccstartmonth =~ s/\D//g;
+	
+	my $mvccstartyear = $actual->{'mv_credit_card_start_year'} || $::Values->{'mv_credit_card_start_year'} || $::Values->{'start_date_year'};
+	   $mvccstartyear =~ s/\D//g;
+	   $mvccstartyear =~ s/\d\d(\d\d)/$1/;
+	   
+	my $startdate = $mvccstartmonth . "/" . $mvccstartyear;
+	   $startdate = '' unless $mvccstartmonth > '0';
+
+	my $issuenumber = $actual->{'mv_credit_card_issue_number'} || $::Values->{'mv_credit_card_issue_number'} ||  $::Values->{'card_issue_number'};
+	   $issuenumber =~ s/\D//g;
+	
+	my $cv2  =  $actual->{'mv_credit_card_cvv2'} || $::Values->{'cvv2'};
+	   $cv2  =~ s/\D//g;
+	   
+	my $cv2indicator = $::Values->{'cv2indicator'} || '2'; # 1=cv2 present, 2=cv2 customer claims not present on card, 5=intentionally not entered
+	   $cv2indicator = '1' if length $cv2;
+	   $::Session->{'cv2indicator'} = $cv2indicator if length $cv2;
+#::logDebug("HSBC".__LINE__.": amt=$amount; currency=$currency; pan=$pan; cardholder=$cardholder; expm=$mvccexpmonth; expy=$mvccexpyear; issue=$issuenumber; cv2=$cv2; cv2indi=$cv2indicator; address=$address");
+
+
+# Lookup unlisted iso codes from country.txt - major country and currency codes identical
+	my ($iso_country_code_numeric, $iso_currency_code_numeric, $iso_currency_symbol, $currencyexponent); 
+
+	if ($currency =~ /GBP/i) {
+		$iso_currency_code_numeric = '826';
+		$iso_currency_symbol = '£';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /EUR/i) {
+		$iso_currency_code_numeric = '978';
+		$iso_currency_symbol = '€';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /USD/i) {
+		$iso_currency_code_numeric = '840';
+		$iso_currency_symbol = '$';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /AUD/i) {
+		$iso_currency_code_numeric = '036';
+		$iso_currency_symbol = '$';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /CAD/i) {
+		$iso_currency_code_numeric = '124';
+		$iso_currency_symbol = '$';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /DKK/i) { 
+		$iso_currency_code_numeric = '206';
+		$iso_currency_symbol = 'kr';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /HKD/i) {
+		$iso_currency_code_numeric = '344';
+		$iso_currency_symbol = '$';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /JPY/i) {
+		$iso_currency_code_numeric = '392';
+		$iso_currency_symbol = 'Y';
+		$currencyexponent = '0';
+		}
+	elsif ($currency =~ /NZD/i) {
+		$iso_currency_code_numeric = '554';
+		$iso_currency_symbol = '$';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /NOK/i) { 
+		$iso_currency_code_numeric = '578';
+		$iso_currency_symbol = 'kr';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /SGD/i) { 
+		$iso_currency_code_numeric = '702';
+		$iso_currency_symbol = '$';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /SEK/i) { 
+		$iso_currency_code_numeric = '752';
+		$iso_currency_symbol = 'kr';
+		$currencyexponent = '2';
+		}
+	elsif ($currency =~ /CHF/i) {
+		$iso_currency_code_numeric = '756';
+		$iso_currency_symbol = 'CHF';
+		$currencyexponent = '2';
+		}
+
+	if ($country =~ /GB|UK/i) {
+		$iso_country_code_numeric = '826';
+		}
+	elsif ($country =~ /US/i) {
+		$iso_country_code_numeric = '840';
+		}
+	else {
+	    $db  = dbref('country') || die ::errmsg("cannot open country table");
+	    $dbh = $db->dbh() || die ::errmsg("cannot get handle for tbl 'country'");
+	    $sth = $dbh->prepare("SELECT isonum FROM country WHERE code = '$country'");
+		$sth->execute();
+		$iso_country_code_numeric = $sth->fetchrow();		
+	};
+
+#::logDebug("HSBC".__LINE__.": country=$country; currency=$currency; currencycode=$iso_currency_code_numeric; countrycode=$iso_country_code_numeric");
+		$iso_currency_code_numeric = '826' unless defined $iso_currency_code_numeric;
+		$iso_country_code_numeric  = '826' unless defined $iso_country_code_numeric;
+#::logDebug("HSBC".__LINE__.": cardref=$cardref; req=$hsbcrequest;  country=$country; currency=$currency; currencycode=$iso_currency_code_numeric");
+#::logDebug("HSBC".__LINE__.": usebill=$usebill; country: $::Values->{b_country}, $actual->{b_country}; $::Values->{country}, $actual->{country}");
+
+#------------------------------------------------------------------------------------------------
+# Go to PaymentAuthenticationServer first, before posting to bank, if doing 3DSecure
+#
+	if ($hsbcrequest eq 'tdspost') {
+#::logDebug("HSBC".__LINE__." started 3DS post to PAS $tdshost");
+# now create encoded string to have returned with data for next post to gateway
+	$md = encode_base64("$pan:$cv2:$cv2indicator:$cardexpiration:$issuenumber:$amount:$x");
+
+		$::Session->{'tdshost'} = $::Scratch->{'tdshost'}  = $tdshost;
+		$::Scratch->{'cardexpiration'}   = $cardexpirationtds;
+		$::Scratch->{'cardholderpan'} = $pan;
+		$::Scratch->{'ccpaclientid'}  = $ccpaclientid;
+		$::Scratch->{'currencyexponent'} = $currencyexponent;
+		$::Scratch->{'md'} = $md;
+		$::Scratch->{'purchaseamount'}  = $iso_currency_symbol . $purchaseamount;
+		$::Scratch->{'purchaseamountraw'}  = $amount;
+		$::Scratch->{'purchasecurrency'}  = $iso_currency_code_numeric;
+		$::Scratch->{'resulturl'}  = $returnurl;
+		
+		$::Scratch->{'issuenumber'} = $issuenumber;
+
+	$::Scratch->{'tdsreturned'} = $::Scratch->{'tdsbounced'} = '';
+	
+	$::Scratch->{'tdsrun'} = $::Scratch->{'tdspause'} = '1';
+#::logDebug("HSBC".__LINE__.": cursymbol=$iso_currency_symbol; pamnt=$purchaseamount; curcode=$iso_currency_code_numeric");	
+	my $hsbctds = $Tag->area({ href => "$hsbctdspage" });
+#::logDebug("HSBC".__LINE__." tdshost=$::Scratch->{'tdshost'}, $tdshost; returnurl=$returnurl; tdspage=$hsbctdspage; tds=$hsbctds");
+$Tag->tag({ op => 'header', body => <<EOB });
+Status: 302 moved
+Location: $hsbctds
+EOB
+
+	 }
+
+#----------------------------------------------------------------------------------------------
+# Header for use in gwpost or other ops
+#
+
+	my $header = <<EOX;
+<?xml version="1.0" encoding="UTF-8" ?> 
+  <EngineDocList>
+    <DocVersion DataType="String">1.0</DocVersion>
+    <EngineDoc>
+      <ContentType DataType="String">OrderFormDoc</ContentType>
+      <User>
+        <ClientId DataType="S32">$clientid</ClientId>
+        <Name DataType="String">$username</Name>
+        <Password DataType="String">$password</Password>
+       </User>
+EOX
+
+
+#----------------------------------------------------------------------------------------------------------
+# Returned from the PAS - read results from PAS and action accordingly
+#
+
+	if ($hsbcrequest eq 'gwpost') {
+#::logDebug("HSBC".__LINE__." started $hsbcrequest; tdsreturn=$tdsreturn");
+
+# Result from PAS is posted as key=val pairs in URL. Run this only if doing 3DS
+	if ($tdsreturn) {
+	  my $tdsdata = ::http()->{entity} ;
+	  my @tdsresult = split(/&/, $$tdsdata);
+#::logDebug("HSBC".__LINE__.": tdsresult=@tdsresult; back=$$tdsdata");	
+	  foreach (@tdsresult) {
+		my($key,$val) = split(/=/,$_);
+		$result{$key} = $val;
+#::logDebug("HSBC".__LINE__.": tdsresult: $key = $val");
+	  }
+#::logDebug("HSBC".__LINE__." result from PAS:" .::uneval(\%result));
+
+	undef $result{'MErrMsg'};
+
+# If PAS 3DSecure result is OK then post to gateway
+# Simply post directly to 'gwpost' and not 'tdspost' if you want to bypass 3DSecure
+# Set various values according to the CcpaResultsCode
+ 	if ($result{'CcpaResultsCode'} =~ /5|6/) {
+#::logDebug("HSBC".__LINE__.": 	code=$result{'CcpaResultsCode'}");
+	$result{'MErrMsg'} = "Authentication failed, please try again or use a different card";
+	return(%result);
+	}
+	elsif ($result{'CcpaResultsCode'} == '10') {
+	$result{'MErrMsg'} = "Payment error, please try again or use a different method";
+	return(%result);
+	}
+
+    elsif ($result{'CcpaResultsCode'} == '0'){
+		$result{'PayerSecurityLevel'} = '2';
+		$result{'PayerAuthenticationCode'} = $result{'CAVV'};
+		$result{'PayerTxnId'} = $result{'XID'};
+		$result{'CardholderPresentCode'} = '13';
+	}
+	elsif ($result{'CcpaResultsCode'} == '1') {
+		$result{'PayerSecurityLevel'} = '5';
+		$result{'CardholderPresentCode'} = '13';
+	
+	}
+	elsif ($result{'CcpaResultsCode'} == '2') {
+		$result{'PayerSecurityLevel'} = '1';
+		$result{'CardholderPresentCode'} = '13';
+	}
+	elsif ($result{'CcpaResultsCode'} == '3') {
+		$result{'PayerSecurityLevel'} = '6';
+		$result{'PayerAuthenticationCode'} = $result{'CAVV'};
+		$result{'PayerTxnId'} = $result{'XID'};
+		$result{'CardholderPresentCode'} = '13';
+	}
+	elsif ($result{'CcpaResultsCode'} == '14') {
+		$result{'CardholderPresentCode'} = '7';
+	}
+	else {
+		$result{'PayerSecurityLevel'} = '4';
+	}
+# end after PAS results parsed
+	$md = decode_base64($result{'MD'});
+	($pan,$cv2,$cv2indicator,$cardexpiration,$issuenumber,$amount,$x) = split /:/, $md;
+
+#::logDebug("HSBC".__LINE__." pan=$pan, cv2=$cv2, cv2ind=$cv2indicator, sid=$::Session->{id}; scpan=$::Scratch->{'cardholderpan'}");
+	$pan ||= $::Scratch->{'cardholderpan'};
+
+
+	} # if tdsreturn
+
+	undef %result unless length $tdsreturn;
+
+#::logDebug("HSBC".__LINE__." result from PAS after massage:" .::uneval(\%result));
+
+	my $cardholderpresentcode = $result{'CardholderPresentCode'} || '7'; # 7 for initial setup without 3DS   $paymenttype = 'PaymentNoFraud' if $cardholderpresentcode =~ /8|10/;
+	my $txid = $Tag->time({ body => "%Y%m%d%H%M%S" }) . $::Session->{id}; 
+	   $txid = $::Values->{'txid'} if ($txtype =~ /^PostAuth|Void|^RePreAuth|^ReAuth|Credit/i and !$pan);
+			   $::Values->{'txid'} = '';
+	my $grouptxid = "rp-" . $txid unless $::Values->{'grouptxid'}; # Pull from db on repeats, else generate for first instance of periodic billing
+			   $::Values->{'grouptxid'} = '';
+   #	$order_id .= $::Session->{id};
+	    $::Session->{'order_id'} = $txid;
+		$::Session->{'HSBCHost'} = '';
+		$::Scratch->{'tdspause'} = '';
+#::logDebug("HSBC".__LINE__.": pan=$pan, cv2=$cv2; exp=$cardexpiration, amnt=$amount; txid=$txid; rpordernumer=$::Values->{rpordernumber}; chpcode=$cardholderpresentcode");
+
+		$cv2 ||= $::Scratch->{'cv2'};
+		$issuenumber ||= $::Scratch->{'issuenumber'};
+#::logDebug("HSBC".__LINE__.": amt=$amount; pan=$pan; cardholder=$cardholder; fname=$fname, lname=$lname, cv2=$cv2; cv2indi=$cv2indicator; address=$address");
+
+   my $xmlOut = $header;
+   
+	  $xmlOut .= <<EOX;   
+      <Instructions>
+        <Pipeline DataType="String">$paymenttype</Pipeline>
+      </Instructions>
+      <OrderFormDoc>
+        <Id DataType="String">$txid</Id>
+        <Mode DataType="String">$txmode</Mode>
+EOX
+
+	   $xmlOut .= <<EOX unless $txtype =~ /void|credit|postauth/i;
+         <Consumer>
+			<BillTo>
+			  <Location>
+				<TelVoice>$telephone</TelVoice>
+				<Email>$email</Email>
+				<Address>
+				  <Title></Title>
+				  <FirstName>$fname</FirstName>
+				  <LastName>$lname</LastName>
+				  <Name>$cardholder</Name>
+EOX
+
+		$xmlOut .= <<EOX if $paymenttype eq 'Payment';
+				  <Street1>$address1</Street1>
+				  <Street2>$address2</Street2>
+				  <Street3>$address3</Street3>
+				  <City>$city</City>
+				  <StateProv>$state</StateProv>
+				  <PostalCode>$zip</PostalCode>
+				  <Country>$iso_country_code_numeric</Country>
+EOX
+
+		$xmlOut .= <<EOX unless $txtype =~ /void|credit|refund|postauth/i;
+				</Address>
+			  </Location>
+			</BillTo>
+          <PaymentMech>
+            <Type DataType="String">CreditCard</Type>
+             <CreditCard>
+              <Number DataType="String">$pan</Number>
+              <Expires DataType="ExpirationDate" Locale="$iso_country_code_numeric">$cardexpiration</Expires>
+EOX
+
+	  $xmlOut .= <<EOX if $startdate;
+			  <StartDate DataType="StartDate" Locale="$iso_country_code_numeric">$startdate</StartDate>
+EOX
+# for 'credit' as payment to card, rather than 'credit' as refund to txid
+#            <Type DataType="String">CreditCard</Type>
+	  $xmlOut .= <<EOX if ($txtype =~ /credit/i and length $pan);
+		<Consumer>
+          <PaymentMech>
+             <CreditCard>
+              <Number DataType="String">$pan</Number>
+              <Expires DataType="ExpirationDate" Locale="$iso_country_code_numeric">$cardexpiration</Expires>
+             </CreditCard>
+          </PaymentMech>
+		</Consumer>
+EOX
+
+	  $xmlOut .= <<EOX unless $txtype =~ /void|credit|refund|postauth/i;
+			  <Cvv2Indicator>$cv2indicator</Cvv2Indicator>
+			  <Cvv2Val>$cv2</Cvv2Val>
+			  <IssueNum>$issuenumber</IssueNum>
+           </CreditCard>
+          </PaymentMech>
+        </Consumer>
+EOX
+	  $xmlOut .= <<EOX;
+        <Transaction>
+          <Type DataType="String">$txtype</Type>
+EOX
+
+	  $xmlOut .= <<EOX  unless $txtype =~ /void|credit|postauth/i;
+	      <CardholderPresentCode>$cardholderpresentcode</CardholderPresentCode>
+EOX
+
+       $xmlOut .= <<EOX if $result{'XID'};
+		  <PayerAuthenticationCode>$result{'CAVV'}</PayerAuthenticationCode>
+		  <PayerTxnId>$result{'XID'}</PayerTxnId>
+		  <PayerSecurityLevel>$result{'PayerSecurityLevel'}</PayerSecurityLevel>
+EOX
+
+	   $xmlOut .= <<EOX if $authcode;
+		  <AuthCode>$authcode</AuthCode>
+EOX
+
+	  
+	   $xmlOut .= <<EOX unless $txtype =~ /void|postauth/i;
+          <CurrentTotals>
+            <Totals>
+              <Total DataType="Money" Currency="$iso_currency_code_numeric">$amount</Total>
+            </Totals>
+          </CurrentTotals>
+EOX
+
+		$xmlOut .= <<EOX;
+        </Transaction>
+EOX
+
+	 $xmlOut .= <<EOX;
+      </OrderFormDoc>
+    </EngineDoc>
+  </EngineDocList>	  
+EOX
+
+#::logDebug("HSBC".__LINE__.": xmlOut=$xmlOut");
+	my $msg = postHSBC($xmlOut);
+#::logDebug("HSBC".__LINE__.": msg returned=$msg");	
+	my $xml = new XML::Simple(Keyattr => 'EngineDocList');
+	my $data = $xml->XMLin("$msg");
+	   $data = $data->{'EngineDoc'}{'OrderFormDoc'};
+#::logDebug("HSBC".__LINE__.": xmlback=".::uneval($data));
+
+#::logDebug("HSBC".__LINE__.": MErrMsg=$result{'MErrMsg'}");		
+#::logGlobal("HSBC: msg returned=$msg") if $result{'CcErrCode'} != '1';	
+	   $result{'TxStatus'} = $data->{'Overview'}{'TransactionStatus'};
+	   $result{'TxType'} = $txtype;
+	   $result{'CardRef'} = $::Session->{'CardRef'};
+	   $result{'Currency'} = $currency if length $currency;
+ 
+	   $result{'HSBCTxId'} = $data->{'Transaction'}{'Id'}{'content'};
+	   $result{'SecurityIndicator'} = $data->{'Transaction'}{'SecurityIndicator'}{'content'};
+	   $result{'AuthCode'} = $data->{'Transaction'}{'AuthCode'}{'content'};
+	   $result{'Type'} = $data->{'Transaction'}{'Auth'}{'content'};
+	   $result{'SendRPvsData'} = $data->{'Transaction'}{'SendPbAvsData'}{'content'};
+	   $result{'ProcReturnMsg'} = $data->{'Transaction'}{'CardProcResp'}{'ProcReturnMsg'}{'content'};
+	   $result{'ProcReturnCode'} = $data->{'Transaction'}{'CardProcResp'}{'ProcReturnCode'}{'content'};
+	   $result{'Status'} = $data->{'Transaction'}{'CardProcResp'}{'Status'}{'content'};
+	   $result{'CcErrCode'} = $data->{'Transaction'}{'CardProcResp'}{'CcErrCode'}{'content'};
+	   $result{'CcReturnMsg'} = $data->{'Transaction'}{'CardProcResp'}{'CcReturnMsg'}{'content'};
+	   $result{'ProcAvsRespCode'} = $data->{'Transaction'}{'CardProcResp'}{'ProcAvsRespCode'}{'content'};
+	   $result{'AvsDisplay'} = $data->{'Transaction'}{'CardProcResp'}{'AvsDisplay'}{'content'};
+	   $result{'CcReturnMsg'} = $data->{'Transaction'}{'CardProcResp'}{'CcReturnMsg'}{'content'};
+	   $result{'CommercialCardType'} = $data->{'Transaction'}{'CardProcResp'}{'CommercialCardType'}{'content'};
+	   $result{'Cvv2Resp'} = $data->{'Transaction'}{'CardProcResp'}{'Cvv2Resp'}{'content'};
+	   $result{'FraudStatus'} = $data->{'Overview'}{'FraudStatus'}{'content'};
+	   $result{'FraudResult'} = $data->{'FraudInfo'}{'FraudResult'}{'content'};
+	   $result{'FraudOrderScore'} = $data->{'FraudInfo'}{'OrderScore'}{'content'};
+
+	   $pass = '1' if $result{'CcErrCode'} == '1'; # no errors, full pass
+
+# Multiple triggers of fraud rules: fail if one rule fails	
+	  if ($pass != '1') {
+	   if ($data->{'FraudInfo'}{'Alerts'} =~ /ARRAY/i) {
+		 for my $i (0 .. 3) {
+		  $result{'FraudRuleId'} = $data->{'FraudInfo'}{'Alerts'}[$i]{'RuleId'}{'content'};
+		  $result{'FraudMsg'} = $data->{'FraudInfo'}{'Alerts'}[$i]{'Message'}{'content'};
+		  $pass = '';
+		  $pass = '1' if charge_param('fraud_' . $result{'FraudRuleId'}) =~ /1/;
+		  $display .= " \"$result{'FraudMsg'}\" <br>" if charge_param('fraud_' . $result{'FraudRuleId'}) =~ /2/;
+			  last if $pass != '1';
+				  }
+				}
+		 else {
+		  $result{'FraudRuleId'} = $data->{'FraudInfo'}{'Alerts'}{'RuleId'}{'content'};
+		  $result{'FraudMsg'} = $data->{'FraudInfo'}{'Alerts'}{'Message'}{'content'};
+		  $pass = '1' if charge_param('fraud_' . $result{'FraudRuleId'}) =~ /1/; # list of all rule IDs plus descriptions
+		  $display = " \"$result{'FraudMsg'}\" " if charge_param('fraud_' .$result{'FraudRuleId'}) =~ /2/; # display the message
+		  }
+		}
+	    
+	   $result{'FraudTotalScore'} = $data->{'FraudInfo'}{'TotalScore'}{'content'};
+	   $result{'FraudResultCode'} = $data->{'FraudInfo'}{'FraudResultCode'}{'content'};
+	   $result{'CardholderPresentCode'} = $data->{'Transaction'}{'CardholderPresentCode'}{'content'};
+#::logDebug("HSBC".__LINE__.": status=$result{'TxStatus'}; authcode=$result{'AuthCode'}; errorcode=$result{CcErrCode}");
+
+	my $hsbcdate = $Tag->time({ body => "%A %d %B %Y, %k:%M:%S, %Z" });
+	my $amountshow =  $amount/'100';
+
+#
+#-------------------------------------------------------------------------------------------------
+# Now to complete things
+#
+
+#::logDebug("HSBC".__LINE__.": rule=$result{FraudRuleId}; ReturnMsg=$result{'CcReturnMsg'}");
+	 $display =~ s/Select \'Accept\' to proceed with the order\.//gi;
+	 $display =~ s/It is recommended that caution is exercised\.//gi;
+	 $::Scratch->{'pspfraudmsg'} = '';
+#::logDebug("HSBC".__LINE__.": ErrCode=$result{CcErrCode}; MStatus=$result{MStatus}; SecureStatus=$result{'SecureStatus'}");
+  if (($pass == '1') and ($hsbcrequest eq 'gwpost')) {
+         $result{'MStatus'} = 'success';
+		 $result{'pop.status'} = 'success';
+		 $::Scratch->{'mstatus'} = 'success';
+         $result{'order-id'} ||= $::Session->{'order_id'};
+		 $::Values->{'mv_payment'} = "Real-time card $::Session->{'CardInfo'}";
+		  if (length $tdsreturn) {
+		   undef $tdsreturn; 
+		   $::Values->{'psp'} = charge_param('psp') || 'HSBC';
+   		   $::Session->{'payment_id'} = $result{'order-id'};
+		   $::Scratch->{'order_id'} = $result{'order-id'};
+		   $::CGI::values{'mv_todo'} = 'submit';
+		   $::Scratch->{'tdsrun'} = '1';
+#::logDebug("HSBC".__LINE__.": result=".::uneval(\%result));
+		   $::Scratch->{'pspfraudmsg'} = "Our bank has flagged this transaction, " 
+					  . $display . 
+					  " and we are obligated to review this order prior to accepting payment
+						and making despatch. Our apologies for any inconvenience." 
+							if $result{'CcErrCode'} != '1';
+
+					$Vend::Session->{'payment_result'} = \%result;
+					Vend::Dispatch::do_process();
+
+					}
+			  }
+   else  {
+         $result{'MStatus'} = $result{'pop.status'} = $::Scratch->{'mstatus'} = 'fail';
+         $result{'order-id'} = $result{'pop.order-id'} = '';
+         $result{'MErrMsg'} = 'Order not taken, card problem. ' . $display; 
+#::logDebug("HSBC".__LINE__.": mstatus=$result{'MStatus'}; MErrMsg=$result{'MErrMsg'}"); 
+	}
+
+	# my $review = $result{'OverView'}{'FraudStatus'} || $result{'OverView'}{'FraudStatus'} 
+#::logDebug("HSBC".__LINE__.": fraudstatus=$result{'FraudStatus'}");
+		if ($result{'FraudStatus'} =~ /Review/i) {
+		$mailreview = <<EOM;
+At $hsbcdate, HSBC TxID $result{HSBCTxId},  for 
+$result{Currency} $amountshow has been marked both "$result{ProcReturnMsg}" and 
+"$result{CcReturnMsg}" with ErrorCode "$result{CcErrCode}". 
+EOM
+
+		$::Tag->email({ to => $mailto, from => $mailto, reply => $mailto,
+						subject => "HSBC txn: blocked for fraud review",
+						body => "$mailreview\n\n",
+					  });
+#::logDebug("HSBC".__LINE__.": blocked, now to mail: $mailreview");
+		};
+	
+		if (($result{'FraudStatus'} =~ /Decline/i) and (charge_param('mail_txn_declined') == '1')) {
+		$::Tag->email({ to => $mailto, from => $mailto, reply => $mailto,
+						subject => "HSBC txn: declined",
+						body => "$mailreview\n\n",
+					  });
+#::logDebug("HSBC".__LINE__.": declined, now to mail: $mailreview");
+		};
+		
+		$mailtxn = "At $hsbcdate you received payment from $cardholder of
+$result{Currency}$amountshow for OrderID $::Session->{'payment_id'}, HSBC txn ID $result{HSBCTxId},
+ AuthCode $result{AuthCode}, from IP $ipaddress with card $result{CardBrand} $result{CardRef}";
+
+		if (($result{'CcReturnMsg'} =~ /Approved/i) and (charge_param('mail_txn_approved') == '1')) {
+		$::Tag->email({ to => $mailto, from => $mailto, reply => $mailto,
+						subject => "HSBC txn: approved",
+						body => "$mailtxn\n\n",
+					  });
+#::logDebug("HSBC".__LINE__.": approved, now to mailto $mailto: $mailtxn");
+		};
+
+#::logDebug("HSBC".__LINE__.": result=".::uneval(\%result));
+		return (%result);
+
+ 	} # if gwpost
+
+}
+
+#
+#--------------------------------------------------------------------------------------------------
+# End of main routine
+#--------------------------------------------------------------------------------------------------
+#
+
+sub postHSBC {
+	 my $self = shift;
+#::logDebug("HSBC".__LINE__."\n###############\nxmlout=$self\n================\n");
+	 my $ua = LWP::UserAgent->new;
+	    $ua->timeout(30);
+	 my $req = HTTP::Request->new('POST' => $gwhost);
+		$req->content_type('text/xml');
+		$req->content_length( length($self) );
+		$req->content($self);
+	 my $res = $ua->request($req);
+	 my $respcode = $res->status_line;
+
+	if ($res->is_success && $res->content){
+		return ($res->content());
+			  }
+	else {
+		$::Session->{'errors'}{'HSBC'} = "No response from the HSBC payment gateway";
+		return $res->status_line;
+     }
+}
+
+package Vend::Payment::HSBC;
+
+1;



More information about the interchange-cvs mailing list