7112529;
#---------------------------------------------------------------
#
# Generated by ISControlISConfigCtrl
#
#---------------------------------------------------------------
#
my $bCompleteOnTechnicalFailures = $::FALSE;
my $bCompleteOnFinancialFailures = $::FALSE;
my $bAuthorize = $::FALSE;
my $bTestMode = $::FALSE;
my $sGTime = '1779794149';
my $sSanity = '957e0fe3b7e319e76a5c547728279407';
my $sProcessScriptURL = 'https://api.clearaccept.com/';
my $sADF01 = 'afwpyszh';
my $sADF02 = 'b8vjcp0w';
my $sADF03 = '2psscc6v3od81ire567tlgh87p';
my $sADF04 = '51 b77ea7653a38d9246211f3712daebe1da054d1883f53254fc0041d3b813ae1ba4dfaa5c4613d36c40578c5ce659e771108cf3adbb68923c5';
my $sADF05 = '4f9c7u65398o9lh4sjm1k7ep8e';
my $sADF06 = '51 b583682c4d1123dede042b7ae54ab004cef057f8f8b72067dcde9483c8b731fd758e8544b0cb5973bf1d74de760c19f77c99b47394714940';
my $sCountriesRequiringStateCodes = 'US';
my $sFieldsAPILive = 'https://hosted.clearaccept.com/fields/v1';
my $sFieldsAPITest = 'https://sandbox-hosted.clearaccept.systems/fields/v1';
my $sPLATFORMID = '8 092b0f30224181de';
my $sADF07 = '';
my $sADF08 = '0 ';
my $sPLATFORMIDTEST = '8 bafa8e5d5ed3a9d7';
my $sPortal_TXNURL = 'https://portal.clearaccept.com/transactions/%s';
my $sPortal_TXNURL_TEST = 'https://sandbox-portal.clearaccept.systems/transactions/%s';
my $sRegistrationDownloadURL = 'https://files.clearaccept.com/applepay-domain-registration-file';
my $sRegistrationFile = 'apple-developer-merchantid-domain-association';
my $sRegistrationFolder = '.well-known';
my $sADFDump = '02010280000003A800000000091B45C28B4A2974988128618EDF3B5766A27EB70053E915164564EFD8983D6DD702D346B1BD2E4009DD9F8D87C4972643A304D206D1D5418C845946F2D8411AC7B03CA94BA529E783EEE7481D4D15EE421FEDA89B559D36DCC33930D849B69F483835D8DCE01B48A03C8102EFC027F81A1C1713861EBE802311298340B56D5357E690E9F2C721175A14268307B2BDE895454325DFE5BCD5C05072E46652E377CF56C82AEAA4ADED643515DF94E1307A22831C3D62B39CB05D99AF75C33608F812009A6A5E36E653742AB45994E802FF943A49C9483FEE18A4B8B2FC64C40B10A8E012CCADE164D92A05A987C5BEB5BAF6702D4AD52775B63C38EB97DBF4B9A6865AAFAFCDA4BC548F25C0BAC0984C9A7E823DA0740F021FD006D0773FD5C8D7A31612CA013A55C04A5689F6C8BE82C06BD61FD90B205A3E03FB581E6013BA8084A660D1D4F4BF1A7134C5AEA1B99D61A4A69F9C5FE59579B8E01A9EFC1E858A2C821A0CEE83D212E041BC0E828F4456BEEB40089D0733CE22162EB509196CBEB2FFEC399928D93CE0FB77E8C7242C2EC83B42E445356C79036C17C7E69D88FEC52238D5D8ECC5EB1BE2B63F4C002B291B3BCD17856BD96F131641423D386C8FA41162290C29115D11A744E89155E34C539B1012CF97C55BBE6B3EA057FC030248133E0D2CD5FEA66D9E4FE16FE362F2BBC61751FD77C5F04204B8F93ADA6058E0D8353871DF5D8D160B91635F8528CA36B4563299722CD78A62155563530A71239E5C6558245FBA34CA695994A478FCC5C62BCA8C66D9C209E764C866C88AB3ED551D8B74FC0999D9F43F07F324C22C555D37C32AF3157368916021884E506F924CA966FA545C6B953828B345EAFFA74685225A86B45A7A48015938759E41CAFCCF736D8877D62A20A02691328E51E72B621469BFE60EE800B5CB3D3E9F44D1A1B9C55F1232E663362381B5CC97E8054590930BE3193BCE4D9E6CAA4242EBF000B5CB3D3E9F44D1B48479917F198184E5DCC97D5CFEC5862969B8D16B1279825B8C3A5688B46D3A16E742B4A82F2A1DA8743243673466B663571DE82DF9A9CBED551D8B74FC0999B35F8392D7ACB55B0F926770D039028E0A606AAE03406824F66C60E5EBBCD77B6AC3595F7EF98E68A63535B159DFA5D79E2C4877F7A64322C3FE513A68820737DC6D097C39DF63F78D031E097B967383B8282F5839F208E30F926770D039028E8863A057092C0FF78EC2960C358C610026DEA066BFFFB27C2FB1DA6E4D80A18A7D8AFBBD2FA4ACB622A30D25D45952D0ED551D8B74FC099908CB3080BDC571843F544628BDDFE9679AAF43A1D84C55F39BD020CE6A0AD896E51F9FEA98C3E1FB991A6C0C304F7F69048D42FE86A9FC5C8B5B295535E4DE7BB140B45BBEDBD598CA8BACB11877350A2949073C02A2F0DADB70154B37A2EABD60C37A7D3B47DB7111805AE172B37D591ED9F12AE62CDD8A34F1658524A85F16DB6B27F1988DB90A';
#
#---------------------------------------------------------------
#
# OCCClearAccept.pl	- ClearAccept Chekout OCC script
#
# Copyright (c) Sellerdeck Limited 2023 All rights reserved
#
# *** Do not change this code unless you know what you are doing ***
#
# This script is called by an eval() function
#
#  $Revision: 38216 $
#
#---------------------------------------------------------------

use strict;

sub GetPspPage1		{	return (ClearAccept::GetPspPage1()			);	}
sub GetPspPage2		{	return (ClearAccept::GetPspPage2()			);	}
sub GetPspPage3		{	return ($::FAILURE, "Unsupported method (GetPspPage3)");	}
sub GetPspCancelPage	{	return ($::FAILURE, "Unsupported method (GetPspCancelPage)");	}
sub ProcessIPN			{	return ($::FAILURE, "Unsupported method (ProcessIPN)");	}
sub GetOfflinePage		{	return (ClearAccept::GetOfflinePage());	}
sub GetOfflineConfirm	{	return (ClearAccept::GetOfflineConfirm());	}

sub UseAuthoriseFile	{	return ($::FALSE);}

package ClearAccept;
#
# Parameters depend on PSP
# Common parameters
#
$ClearAccept::PSP_MERCHANT_ID = $sADF01;
$ClearAccept::PSP_MERCHANT_SITE_ID = $sADF02;
#
# ClearAccept uses multiple APIs, each has its own Client ID and Secret key which will
# give us a token for each API so we store the details in arrays accessible by an API flag
# At the time of writing only the TRANSAC API is used online
#
# TRANSACT API:-
$ClearAccept::TRANSACT = 0;
@ClearAccept::PSP_CLIENT_ID[$ClearAccept::TRANSACT] = $sADF03;
@ClearAccept::PSP_SECRET_KEY[$ClearAccept::TRANSACT] = ACTINIC::DecryptPspParam($sADF04);
@ClearAccept::TOKEN[$ClearAccept::TRANSACT];
@ClearAccept::EXPIRY_TIME[$ClearAccept::TRANSACT] = 0;

$ClearAccept::HTTP_TIMEOUT = 30;
$ClearAccept::HTTP_MAX_RETRIES = 3;
$ClearAccept::HTTP_RETRY_INTERVAL = 1;				# seconds before retry

$ClearAccept::PORT = 443;
$sProcessScriptURL =~ /https?:\/\/(.*?):?\d*(\/.*)$/;
$ClearAccept::PROCESS_SCRIPT_HOST = $1;			# extract host name e.g. sandbox-api.clearaccept.systems

$ClearAccept::PSP_API_VERSION = 1;

$ClearAccept::CONTACTMSG = ACTINIC::GetPhrase(-1, 1964);
$ClearAccept::RETRYMSG = ". Please try again or choose a different payment method";
$ClearAccept::ERRORMSG = 'A problem occurred while processing your payment request' . $ClearAccept::RETRYMSG;
$ClearAccept::DECLINEMSG = 'Your payment has been declined' . $ClearAccept::RETRYMSG;
$ClearAccept::PENDINGMSG = 'Sorry, but we have not been able to confirm your payment. Please contact us quoting order %s';
$ClearAccept::UNKNOWNSTATEMSG = 'Sorry, we are unable to determine if your payment has been confirmed for order %s due to a network error. Contact sales before trying to pay again';

$ClearAccept::IMMEDIATE_CAPTURE = $bAuthorize ? $::TRUE : $::FALSE;

$ClearAccept::TESTMODE	= $bTestMode;
if ($bTestMode)
	{
	$ClearAccept::TESTMESSAGE = 'ClearAccept is running in Test mode</br>';	 # warn we are in test mode
	$ClearAccept::IDENTITYHOST='sandbox-identity-api.clearaccept.systems';
	$ClearAccept::FIELDSHOST = $sFieldsAPITest;
	$ClearAccept::PSP_PLATFORM_ID = ACTINIC::DecryptPspParam($sPLATFORMIDTEST);
	}
else
	{
	$ClearAccept::TESTMESSAGE = '';
	$ClearAccept::IDENTITYHOST='identity-api.clearaccept.com';
	$ClearAccept::FIELDSHOST = $sFieldsAPILive;
	$ClearAccept::PSP_PLATFORM_ID = ACTINIC::DecryptPspParam($sPLATFORMID);
	}
$ClearAccept::URL_OK = "Act_ClearAcceptOK.html";
$ClearAccept::URL_ERROR = "Act_ClearAcceptError.html";

#######################################################
#
# GetPspPage1 - ClearAccept page 1 gets a ClearAccept
#						Payment Request ID and a field token
#
#	Requires: 	$::g_InputHash
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub GetPspPage1
	{
#? ACTINIC::ASSERT($#_ == -1, "Invalid argument count in GetPspPage1 ($#_)", __LINE__, __FILE__);
	LogData("ClearAccept::GetPspPage1: Started");
	my ($nStatus, $sError);
	if ('GetShippingOptions' eq $::g_InputHash{'SUBACTION'})
		{
		LogData("ClearAccept::GetPspPage1: GetShippingOptions");
		if ($::g_InputHash{'VALIDATECART'} eq 'true')
			{
			@Response = ActinicOrder::ValidateStart($::TRUE);
			if ($Response[0] == $::BADDATA)
				{
				ACTINIC::PrintPage($Response[1], undef);
				exit;
				}
			}
		($nStatus, $sError) = GetWalletShippingOptions();
		if ($nStatus != $::SUCCESS)
			{
			#
			# Failed to handle Wallet Shipping Option request
			#
			RecordErrors("ClearAccept::GetPspPage1 $sError");
			return ($::FAILURE, $sError);
			}
		LogData("ClearAccept::GetPspPage1: Finished successfully");
		exit;													# all done
		}
	elsif ('UpdateShipping' eq $::g_InputHash{'SUBACTION'})
		{
		LogData("ClearAccept::GetPspPage1: UpdateShipping");
		($nStatus, $sError) = UpdateWalletShipping();
		if ($nStatus != $::SUCCESS)
			{
			#
			# Failed to handle Wallet Shipping Update request
			#
			RecordErrors("ClearAccept::GetPspPage1 $sError");
			return ($::FAILURE, $sError);
			}
		LogData("ClearAccept::GetPspPage1: Finished successfully");
		exit;													# all done
		}
	elsif ('UpdateCoupon' eq $::g_InputHash{'SUBACTION'})
		{
		LogData("ClearAccept::GetPspPage1: UpdateCoupon");

		my $sCouponCode = $::g_InputHash{'COUPONCODE'};
		#
		# strip any trailing or leading spaces
		#
		$sCouponCode =~ s/^\s*//;
		$sCouponCode =~ s/\s*$//;

		if ($sCouponCode eq "")
			{
			return ($::FAILURE, 'Coupon Code not entered');
			}
		elsif (('true' ne $::g_InputHash{'CARTPAGE'}) ||
				 (!$$::g_pDiscountBlob{'COUPON_ON_CART'} &&
				 !$$::g_pDiscountBlob{'COUPON_ON_CHECKOUT'}))
			{
			return ($::FAILURE, 'Coupon Code not allowed here');
			}

		($nStatus, $sError) = UpdateCoupon($sCouponCode);
		if ($nStatus != $::SUCCESS)
			{
			#
			# Failed to handle Coupon Update request
			#
			RecordErrors("ClearAccept::GetPspPage1 $sError");
			return ($::FAILURE, $sError);
			}
		LogData("ClearAccept::GetPspPage1: Finished successfully");
		exit;													# all done
		}
	elsif ('CompleteOrderAndPayment' eq $::g_InputHash{'SUBACTION'})
		{
		LogData("ClearAccept::GetPspPage1: CompleteOrderAndPayment");
		my $bFromCart = ('true' eq $::g_InputHash{'CARTPAGE'}) ? $::TRUE : $::FALSE;
		my $bAllowPersist = (1 == $::g_InputHash{'ALLOWPERSIST'}) ? $::TRUE : $::FALSE;
		#
		# Fool shipping into thinking this is the shipping confirmation page
		#
		$::bPPConfirmationPage = $::TRUE;
		($nStatus, $sError) = CompleteWalletOrder($bFromCart, $bAllowPersist);
		if ($nStatus != $::SUCCESS)
			{
			#
			# Failed to handle Wallet Order Completion request
			#
			RecordErrors("ClearAccept::GetPspPage1 $sError");
			return ($::FAILURE, $sError);
			}
		LogData("ClearAccept::GetPspPage1: Finished successfully");
		exit;													# all done
		}
	elsif ('Cancel' eq $::g_InputHash{'SUBACTION'})
		{
		LogData("ClearAccept::GetPspPage1: Cancel");
		if ('true' eq $::g_InputHash{'CARTPAGE'})
			{
			$::Session->ClearInCheckout();
			if ($ACTINIC::B2B->Get('UserDigest'))
				{
				#
				# We are still logged in so restore the account contact details
				#
				my ($Status, $sMessage, $pBuyer, $pAccount) = ACTINIC::GetBuyerAndAccount($ACTINIC::B2B->Get('UserDigest'));
				if ($Status == $::SUCCESS)
					{
					($Status, $sMessage) = ACTINIC::CaccSetCheckoutFields($pBuyer, $pAccount);
					}
				}
			else
				{
				#
				# Restore checkout and contact details from cookie
				# only if from cart or first checkout page
				#
	 			if ($::FAILURE == $::Session->CookieStringToContactDetails())		# if there isn't contact details cookie
	 				{
	 				$::Session->UpdateCheckoutInfo({}, {}, {}, {}, {}, {}, {});	# reset the data structure
	 				}
				main::GetCheckoutStatus();
				}
			}
		#
		# All done, save the session and send JSON response
		#
		my %hJsonResponse;
		$hJsonResponse{'success'} = 1;
		SaveSessionAndSendResponse(\%hJsonResponse);
		LogData("ClearAccept::GetPspPage1: Finished successfully");
		exit;													# all done
		}
	elsif (defined $::g_InputHash{'SUBACTION'})
		{
		LogData("ClearAccept::GetPspPage1: Unrecognised sub action [" . $::g_InputHash{'SUBACTION'} . ']');
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	#
	# Determine if comming from CART or payment page
	# Checkout Page 0 is also treated as cart page
	#
	my $bFromCart = ('true' eq $::g_InputHash{'CARTPAGE'}) ? $::TRUE : $::FALSE;
	my ($hPaymentRequest, $hResponse);
	my $bAllowCreate = $::TRUE;
	($nStatus, $sError, $hPaymentRequest) = GetPaymentRequest($bAllowCreate);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot get a Payment Request
		#
		RecordErrors(sprintf("ClearAccept::GetPspPage1: GetPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $sError);
		}

	if (AlreadyPaid($hPaymentRequest))
		{
		ReturnReceiptUrl();
		exit;
		}
	#
	# Check to see if we have a re-usable request. Only re-use if
	# status is missing_token otherwise, if the buyer has refreshed
	# payment page during payment confirmation it will cause a problem
	# See SDCS-61328
	#
	if ('missing_token' ne $hPaymentRequest->{'status'})
		{
		#
		# We have a previous Payment Request but it is not suitable so we must create a new one
		#
		LogData(sprintf("ClearAccept::GetPspPage1: Existing payment request status is '%s' so creating new payment request", $hPaymentRequest->{'status'}));
		($nStatus, $sError, $hPaymentRequest) = CreatePaymentRequest($bAllowCreate);
		if ($nStatus != $::SUCCESS)
			{
			#
			# Cannot create new payment request
			#
			return ($nStatus, $sError);
			}
		}
	if ($bFromCart)
		{
		($hResponse) =
			{
			'paymentRequestId' => $hPaymentRequest->{'paymentRequestId'}
			};
		}
	else
		{
		#
		# When we are processing the payment page, we may have created the
		# payment request prior to the address selection page in which case
		# we need to update the addresses now
		#
		my %hContacts = {};
		$hContacts{'BillContact'} = \%::g_BillContact;
		$hContacts{'ShipContact'} = \%::g_ShipContact;
		$hContacts{'SeparateShipAddress'} = $::g_BillContact{'SEPARATE'};
		$hContacts{'INVOICE_COUNTRY_CODE'} = $::g_LocationInfo{'INVOICE_COUNTRY_CODE'};
		$hContacts{'DELIVERY_COUNTRY_CODE'} = $::g_LocationInfo{'DELIVERY_COUNTRY_CODE'};
		($nStatus, $sError, $hResponse) = UpdatePaymentRequest($hPaymentRequest->{'paymentRequestId'}, \%hContacts);
		if ($nStatus != $::SUCCESS)
			{
			return ($::FAILURE, $sError);
			}
		($nStatus, $sError, $hResponse) = GetFieldToken($hPaymentRequest->{'paymentRequestId'});
		if ($nStatus != $::SUCCESS)
			{
			#
			# Cannot get a Field Token
			#
			RecordErrors(sprintf("ClearAccept::GetPspPage1: GetFieldToken returned [%s]", $sError));
			return ($::FAILURE, $ClearAccept::ERRORMSG);
			}
		}
	#
	# Restore the selected PSP as it has not actually been selected at this time
	#
	$::g_PaymentInfo{'METHOD'} = $::g_PreviousPaymentMethod;
	SaveSessionAndSendResponse($hResponse);
	LogData("ClearAccept::GetPspPage1: Finished successfully");

	return ($::SUCCESS, '');
	}

#######################################################
#
# GetPspPage2 - ClearAccept page 2 confirm the payment
#
#	Requires: 	$::g_InputHash
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub GetPspPage2
	{
#? ACTINIC::ASSERT($#_ == -1, "Invalid argument count in GetPspPage2 ($#_)", __LINE__, __FILE__);
	LogData("ClearAccept::GetPspPage2: Started");
	my ($nStatus, $sError, $hPaymentRequest, $hResponse);
	my $bAllowPersist = $::FALSE;
	if (defined $::g_InputHash{'ALLOWPERSIST'} &&
		 $::g_InputHash{'ALLOWPERSIST'})
		{
		$bAllowPersist = $::TRUE;
		}
	my $sToken = $::g_InputHash{'CARDTOKEN'};
	#
	# Get our Payment Request
	#
	($nStatus, $sError, $hPaymentRequest) = GetPaymentRequest();
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot get the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::GetPspPage2: GetPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	if ('confirmed' eq $hPaymentRequest->{'status'})
		{
		#
		# Unexpected Payment Request status (already confirmed)
		#
		RecordErrors(sprintf("ClearAccept::GetPspPage2: unexpected request status [%s]", $hPaymentRequest->{'status'}));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	#
	# Phase 1 is just an update of the Payment Request with a token
	#
	if (1 == $::g_InputHash{'PHASE'})
		{
		#
		# Validate payment info`
		#
		$sError = main::ValidatePayment($::TRUE, 'text');
		if (length $sError > 0)
			{
			LogData(sprintf("ClearAccept::GetPspPage2: ValidatePayment returned [%s]", $sError));
			return ($::FAILURE, $sError);
			}
		#
		# Create the Sellerdek Order (allocates the order number)
		#
		($nStatus, $sError) = main::CompleteOrder();
		if ($nStatus != $::SUCCESS)
			{
			#
			# Cannot complete the Sales Order
			#
			RecordErrors(sprintf("ClearAccept::GetPspPage2: Complete Order returned [%s]", $sError));
			return ($::FAILURE, $sError);
			}
		my %hContacts = {};
		$hContacts{'BillContact'} = \%::g_BillContact;
		$hContacts{'ShipContact'} = \%::g_ShipContact;
		$hContacts{'SeparateShipAddress'} = $::g_BillContact{'SEPARATE'};
		$hContacts{'INVOICE_COUNTRY_CODE'} = $::g_LocationInfo{'INVOICE_COUNTRY_CODE'};
		$hContacts{'DELIVERY_COUNTRY_CODE'} = $::g_LocationInfo{'DELIVERY_COUNTRY_CODE'};
		($nStatus, $sError, $hResponse) = UpdatePaymentRequest($hPaymentRequest->{'paymentRequestId'}, \%hContacts, $sToken, $bAllowPersist);
		if ($nStatus != $::SUCCESS)
			{
			return ($::FAILURE, $sError);
			}
		SaveSessionAndSendResponse({status=>1});# just return 1, the JS just needs to know the update was done
		LogData("ClearAccept::GetPspPage2: Phase 1 Finished Successfully");
		exit;
		}
	#
	# Phase 2 performs the transaction confirmation
	# Lock the session to prevent duplicate payments
	#
	LogData("ClearAccept::GetPspPage2: Getting session lock");
	if ($::Session->LockSession())					# file will be released on exit
		{
		LogData("ClearAccept::GetPspPage2: Locked Session");
		}
	#
	# Make sure the Payment Request is for the current order
	#
	if ($::g_PaymentInfo{'ORDERNUMBER'} ne $hPaymentRequest->{'merchantReferences'}{'transactionReference'})
		{
		RecordErrors(sprintf("ClearAccept::GetPspPage2: Payment request %s for different order %s, expected %s",
			$hPaymentRequest->{'paymentRequestId'},
			$hPaymentRequest->{'merchantReferences'}{'transactionReference'},
			$::g_PaymentInfo{'ORDERNUMBER'}));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	#
	# Record the Payment Request to an OCC file so we have a record of it in case the confirm fails
	#
	($nStatus, $sError) = RecordPaymentRequest($hPaymentRequest);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot record the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::GetPspPage2: RecordPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $sError);
		}
	($nStatus, $sError, $hPaymentRequest) = ConfirmPaymentRequest($hPaymentRequest->{'paymentRequestId'}, $sToken, $bAllowPersist);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot confirm the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::GetPspPage2: ConfirmPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $sError);
		}
	($nStatus, $sError) = VerifyAndRecordConfirmation($hPaymentRequest);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot record confirmation
		#
		return ($::FAILURE, $sError);
		}
	#
	# All done, save the session and send JSON response
	#
	my %hJsonResponse;
	$hJsonResponse{'success_url'} = main::GetReceiptUrl($::g_PaymentInfo{'ORDERNUMBER'});
	SaveSessionAndSendResponse(\%hJsonResponse);
	#
	# We have sent our response so just exit
	#
	LogData("ClearAccept::GetPspPage2: Phase 2 Finished Successfully");
	exit;
	}

#######################################################
#
# GetOfflinePage - ClearAccept page for new offline payment
#	using new card or cvv
#
#	Requires: 	$::g_InputHash
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#				2 - HTML of page if not error
#
#######################################################

sub GetOfflinePage
	{
#? ACTINIC::ASSERT($#_ == -1, "Invalid argument count in GetOfflinePage ($#_)", __LINE__, __FILE__);
	#
	# Render the page that will use hosted card fields for an offline payment
	#
	LogData("ClearAccept::GetOfflinePage: Started");
	my %VariableTable;
	$VariableTable{$::VARPREFIX.'ORDERNUMBER'}	= $::g_InputHash{'ON'};		
	my $nAmount = $::g_InputHash{'AMOUNT'};
	my @Response = ActinicOrder::FormatPrice($nAmount, $::TRUE, $::g_pCatalogBlob);
	if ($Response[0] != $::SUCCESS)
		{
		return (@Response);
		}

	$VariableTable{$::VARPREFIX.'AMOUNT'}	= $Response[4];
	$VariableTable{$::VARPREFIX.'FIELDTOKEN'}	= $::g_InputHash{'FIELDTOKEN'};
	$VariableTable{$::VARPREFIX.'REQUESTID'}	= $::g_InputHash{'PR'};
	$VariableTable{$::VARPREFIX.'SCRIPTURL'} = main::GetCheckoutBaseUrl(); # order script URL
	$VariableTable{$::VARPREFIX.'SCRIPTURL'} =~ s/[?#].*$//; # strip query string
	$VariableTable{$::VARPREFIX.'SHOPPATH'} = $$::g_pSetupBlob{'SSL_CATALOG_URL'};	
	$VariableTable{$::VARPREFIX.'CARDDETAILS'}	= $::g_InputHash{'CD'};	
	$VariableTable{$::VARPREFIX.'CVVONLY'}	= $::g_InputHash{'CD'} ne "" ? "true" : "false";
	$VariableTable{$::VARPREFIX.'FIELDSHOST'}	= $ClearAccept::FIELDSHOST;
	@Response = ACTINIC::TemplateFile(ACTINIC::GetPath() . 'Act_ClearAcceptOffline.html', \%VariableTable); # make the substitutions	
	LogData("ClearAccept::GetOfflinePage: Finished");
	return @Response;
	}

#######################################################
#
# GetOfflineConfirm - ClearAccept confirmation for new offline payment
#	using new card or cvv
#
#	Requires: 	$::g_InputHash
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#				2 - name of page for response
#
#######################################################

sub GetOfflineConfirm
	{
#? ACTINIC::ASSERT($#_ == -1, "Invalid argument count in GetOfflineConfirm ($#_)", __LINE__, __FILE__);
	LogData("ClearAccept::GetOfflineConfirm: Started");
	my ($nStatus, $sError, $hPaymentRequest, $hResponse);

	my $bAllowPersist = $::FALSE;	# not saving cards for offline orders

	$::g_PaymentInfo{'ORDERNUMBER'} = $::g_InputHash{'ON'}; # set order number from query string

	#
	# Fetch the Payment Request
	#
	($nStatus, $sError, $hPaymentRequest) = ReadPaymentRequest($::g_InputHash{'PR'});
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot read Payment Request
		#
		return ($::FAILURE, $sError);
		}
	#
	# Record the Payment Request to an OCC file so we have a record of it in case the confirm fails
	#
	($nStatus, $sError) = RecordPaymentRequest($hPaymentRequest);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot record the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::GetOfflineConfirm: RecordPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $sError);
		}
	($nStatus, $sError, $hPaymentRequest) = ConfirmPaymentRequest($hPaymentRequest->{'paymentRequestId'}, $::g_InputHash{'CARDTOKEN'}, $bAllowPersist);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot confirm the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::GetOfflineConfirm: ConfirmPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $sError, $ClearAccept::URL_ERROR);
		}
	$::g_InputHash{'ACTION'} = sprintf("OFFLINE_AUTHORIZE_%d", $::PAYMENT_CLEARACCEPT);
	($nStatus, $sError) = VerifyAndRecordConfirmation($hPaymentRequest);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot record confirmation
		#
		return ($::FAILURE, $sError, $ClearAccept::URL_ERROR);
		}
	LogData("ClearAccept::GetOfflineConfirm: Finished Successfully");

	return ($::SUCCESS, '', $ClearAccept::URL_OK);
	}

#######################################################
#
# GetWalletShippingOptions - Record the shipping and
#										determine the shipping options
#
#	Returns:	0 - status $::SUCCESS if successful
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub GetWalletShippingOptions
	{
	#
	# Check if the cart has tangible products
	#
	my %hJsonResponse;
	$hJsonResponse{'status'} = 'success';			# assume we shall succeed

	my $bCartHasTangibleGoods = ACTINIC::CartHasTangibleGoods();
	my ($nStatus, $sError) = GetWalletShippingAddress($bCartHasTangibleGoods);
	if ($nStatus != $::SUCCESS)
		{
		return ($::FAILURE, $sError);
		}
	#
	# Validate tax and shipping info`
	#
	($nStatus, $sError) = ValidateShippingAndTax();
	if ($nStatus != $::SUCCESS)
		{
		$hJsonResponse{'status'} = 'fail';
		$hJsonResponse{'invalid'} = $sError;
		ACTINIC::PrintJSON(ACTINIC::EncodeJson(\%hJsonResponse));
		return ($::SUCCESS);
		}
	else
		{
		#
		# Prepare the response
		# Collect the cart totals
		#
		($nStatus, $sError) = ActinicOrder::GetShoppingCartTotalsJSON(\%hJsonResponse);
		if ($nStatus != $::SUCCESS)
			{
			RecordErrors("ClearAccept::GetWalletShippingOptions: GetShoppingCartTotalsJSON returned $sError");
			return ($::FAILURE, $sError);
			}
		#
		# Add the delivery options to the response hash
		#
		($nStatus, $sError) = ActinicOrder::GetDeliveryOptionsAsJSON(\%hJsonResponse);
		if ($nStatus != $::SUCCESS)
			{
			RecordErrors("ClearAccept::GetWalletShippingOptions: GetDeliveryOptionsAsJSON returned $sError");
			return ($::FAILURE, $sError);
			}
		}
	#
	# We must save the session otherwise later
	# calls will not calcualte correct shipping
	#
	LogData("ClearAccept::GetWalletShippingOptions: Response is " . ACTINIC::EncodeJson(\%hJsonResponse));
	SaveSessionAndSendResponse(\%hJsonResponse);
	return ($::SUCCESS);
	}

#######################################################
#
# GetWalletShippingAddress - Collect the shipping address
#										from the input hash
#
#	Input:	0 - true if cart has tangible products
#
#	Returns:	0 - status $::SUCCESS if successful
#				1 - error message if status not $::SUCCESS
#				2 - country changed
#
#######################################################

sub GetWalletShippingAddress
	{
	my $bCartHasTangibleGoods = shift;
	my ($nStatus, $sError);
	#
	# Read the shipping address from input
	#
	main::GetContactFromInput('DELIVER', \%::g_ShipContact);
	#
	# Set the input location info fields
	#
	$::g_LocationInfo{'DELIVERY_COUNTRY_CODE'} = $::g_InputHash{'LocationDeliveryCountry'};
	#
	# Change GB country code to UK
	#
	$::g_LocationInfo{'DELIVERY_COUNTRY_CODE'} =~ s/^GB$/UK/;
	$::g_LocationInfo{'DELIVERY_REGION_CODE'} = ActinicOrder::GetSellerDeckRegion($::g_LocationInfo{'DELIVERY_COUNTRY_CODE'}, $::g_ShipContact{'ADDRESS4'}, $::g_ShipContact{'POSTALCODE'});
	#
	# Get the name of the country
	#
	$::g_InputHash{'DELIVERCOUNTRY'} = ACTINIC::GetCountryName($::g_LocationInfo{'DELIVERY_COUNTRY_CODE'});
	#
	# Assume separate ship address or shipping address
	# will  be overwritten by the billing address
	#
	$::g_BillContact{'SEPARATE'} = $::g_LocationInfo{'SEPARATESHIP'} = $::TRUE;
	#
	# We don't call ValidateShipContact here as the code is too specific to normal checkout
	# Instead we call directly key components of ValidateShipContact
	#
	if ($::g_LocationInfo{'DELIVERY_REGION_CODE'} eq $ActinicOrder::UNDEFINED_REGION)
		{
		#
		# Region is undefined, check if need it
		#
		if (ActinicOrder::StateRequiredForValidation('Delivery', $::g_LocationInfo{'DELIVERY_COUNTRY_CODE'}))
			{
			#
			# Does not matter if we have no tangible goods to ship
			#
			if ($bCartHasTangibleGoods)
				{
				if ($::s_sDeliveryRegionCode eq '')
					{
					return ($::FAILURE, ACTINIC::GetPhrase(-1, 196));
					}
				$::g_LocationInfo{'DELIVERY_REGION_CODE'} = $::s_sDeliveryRegionCode; # set default
				}
			}
		}
	return ($::SUCCESS, '');
	}

#######################################################
#
# UpdateWalletShipping - Updates the shipping for the
#									selected shipping option
#
#	Returns:	0 - status $::SUCCESS if successful
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub UpdateWalletShipping
	{
	my ($nStatus, $sError);
	my %hJsonResponse;
	$hJsonResponse{'status'} = 'success';			# assume we shall succeed
	#
	# Validate tax and shipping info`
	#
	($nStatus, $sError) = ValidateShippingAndTax();
	if ($nStatus != $::SUCCESS)
		{
		$hJsonResponse{'status'} = 'fail';			# cannot ship
		$hJsonResponse{'invalid'}{'message'} = $sError;
		ACTINIC::PrintJSON(ACTINIC::EncodeJson(\%hJsonResponse));
		return;
		}
	else
		{
		#
		# Collect the cart totals
		#
		($nStatus, $sError) = ActinicOrder::GetShoppingCartTotalsJSON(\%hJsonResponse);
		if ($nStatus != $::SUCCESS)
			{
			RecordErrors("ClearAccept::UpdateWalletShipping: GetShoppingCartTotalsJSON returned $sError");
			return ($::FAILURE, $sError);
			}
		}
	#
	# We must save the session otherwise later
	# calls will not calcualte correct shipping
	#
	LogData("ClearAccept::UpdateWalletShipping: Response is " . ACTINIC::EncodeJson(\%hJsonResponse));
	SaveSessionAndSendResponse(\%hJsonResponse);
	return ($::SUCCESS);
	}

#######################################################
#
# UpdateCoupon - Updates the coupon code, if valid
#
#	Input:	0 - coupon code
#
#	Returns:	0 - status $::SUCCESS if successful
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub UpdateCoupon
	{
	my $sCouponCode = shift;
	my ($nStatus, $sError, $sHighlanderCoupon);

	my %hJsonResponse;
	$hJsonResponse{'status'} = 'success';			# assume we shall succeed
	#
	# Check coupon code
	#
	($nStatus, $sError) = $::Session->GetCartObject();	# be sure we have discounting loaded
	if ($nStatus != $::SUCCESS)
		{
		return ($::FAILURE, $sError);
		}
	($nStatus, $sError, $sHighlanderCoupon) = ActinicDiscounts::ValidateCoupon($sCouponCode);
	if ($nStatus != $::SUCCESS)
		{
		$hJsonResponse{'status'} = 'fail';
		$hJsonResponse{'invalid'}{'message'} = $sError;
		ACTINIC::PrintJSON(ACTINIC::EncodeJson(\%hJsonResponse));
		return;
		}
	if (ActinicDiscounts::IsMultipleFixedPriceCouponAccepted())
		{
		my $sCoupons = $::Session->GetCSVCouponCodes();
		my $sNewEntry = $sCouponCode;
		if ($sCoupons =~ /\b$sNewEntry\b/)   	# use word boundary to avoid partial matches
			{
			$::Session->SetCouponError(ACTINIC::GetPhrase(-1, 3080, $sNewEntry));
			}
		else
			{
			ActinicDiscounts::AppendCouponCode($sCouponCode);
			$::Session->SetCouponError('');		# reset coupon error	
			}
		}
	else
		{
		$::g_PaymentInfo{'COUPONCODE'} = $sCouponCode;
		$::Session->SetCoupon($::g_PaymentInfo{'COUPONCODE'});
		}
	if ($sHighlanderCoupon)							# do we need to save this as highlander coupon
		{
		$::Session->SetHighlanderCoupon($sCouponCode);
		}
	#
	# Collect the cart totals
	#
	($nStatus, $sError) = ActinicOrder::GetShoppingCartTotalsJSON(\%hJsonResponse);
	if ($nStatus != $::SUCCESS)
		{
		RecordErrors("ClearAccept::UpdateCoupon: GetShoppingCartTotalsJSON returned $sError");
		return ($::FAILURE, $sError);
		}
	#
	# We must save the session to preserve the selected coupon code
	#
	SaveSessionAndSendResponse(\%hJsonResponse);
	return ($::SUCCESS);
	}

#######################################################
#
# CompleteWalletOrder - Complete a wallet order and
#								confirm payment
#
#	Input:	0 - from cart (true/false)
#				1 - persist card token? (0/1) 1=>Yes
#
#	Returns:	0 - status $::SUCCESS if successful
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub CompleteWalletOrder
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in CompleteWalletOrder ($#_)", __LINE__, __FILE__);
	LogData("ClearAccept::CompleteWalletOrder: Started");
	my $bFromCart = shift;
	my $bAllowPersist = shift;
	my %hJsonResponse;
	my ($nStatus, $sError, $pPaymentRequest, $hResponse);
	#
	# Get the Payment Request
	#
	($nStatus, $sError, $pPaymentRequest) = GetPaymentRequest();
	if ($nStatus != $::SUCCESS)
		{
		RecordErrors(sprintf("ClearAccept::CompleteWalletOrder: GetPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	#
	# Make sure not already confirmed
	#
	if ('confirmed' eq $pPaymentRequest->{'status'})
		{
		RecordErrors(sprintf("ClearAccept::CompleteWalletOrder: unexpected request status [%s]", $pPaymentRequest->{'status'}));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	#
	# Perform sanity checks
	#
	my $nTotal = ActinicOrder::GetOrderTotal($::TRUE);
	my $sCurrency = GetOrderCurrencyCode();
	if (($nTotal != $pPaymentRequest->{'amount'}) ||
		 ($sCurrency ne $pPaymentRequest->{'currency'}))
		{
		RecordErrors(sprintf("ClearAccept::CompleteWalletOrder: order total [%s%s] not as expected [%s%s]",
				$nTotal, $sCurrency, $pPaymentRequest->{'amount'}, $pPaymentRequest->{'currency'}));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	my %hWalletBillingContact = {};
	GetContactDetailsFromPaymentRequest($pPaymentRequest, \%hWalletBillingContact);
	#
	# Create the Sellerdek Order (allocates the order number)
	#
	($nStatus, $sError) = main::CompleteOrder();
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot complete the Sales Order
		#
		RecordErrors(sprintf("ClearAccept::CompleteWalletOrder: Complete Order returned [%s]", $sError));
		return ($::FAILURE, $sError);
		}
	#
	# Update request with order number, token and mandatory fields
	# We have to send all fields so we send our shipping
	# address but the billing address must come from Apple Pay
	#
	my %hContacts = {};
	$hContacts{'BillContact'} = \%hWalletBillingContact;
	$hContacts{'ShipContact'} = \%::g_ShipContact;
	$hContacts{'SeparateShipAddress'} = ($pPaymentRequest->{'customerInfo'}{'sameShippingAndBilling'}) ? $::FALSE : $::TRUE;
	$hContacts{'INVOICE_COUNTRY_CODE'} = $::g_LocationInfo{'WALLET_COUNTRY_CODE'};
	$hContacts{'DELIVERY_COUNTRY_CODE'} = $::g_LocationInfo{'DELIVERY_COUNTRY_CODE'};

	($nStatus, $sError, $hResponse) = UpdatePaymentRequest($pPaymentRequest->{'paymentRequestId'}, \%hContacts, $pPaymentRequest->{'token'}, $bAllowPersist);
	if ($nStatus != $::SUCCESS)
		{
		return ($::FAILURE, $sError);
		}
	#
	# Record the Payment Request to an OCC file so we have a record of it in case the confirm fails
	#
	($nStatus, $sError) = RecordPaymentRequest($pPaymentRequest);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot record the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::CompleteWalletPayment: RecordPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $sError);
		}
	($nStatus, $sError, $pPaymentRequest) = ConfirmPaymentRequest($pPaymentRequest->{'paymentRequestId'}, $pPaymentRequest->{'token'}, $bAllowPersist);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot confirm the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::CompleteWalletPayment: ConfirmPaymentRequest returned [%s]", $sError));
		return ($::FAILURE, $sError);
		}
	($nStatus, $sError) = VerifyAndRecordConfirmation($pPaymentRequest);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot record confirmation
		#
		return ($::FAILURE, $sError);
		}
	#
	# All done, save the session and send JSON response
	#
	$hJsonResponse{'success_url'} = main::GetReceiptUrl($::g_PaymentInfo{'ORDERNUMBER'});
	SaveSessionAndSendResponse(\%hJsonResponse);
	LogData("ClearAccept::CompleteWalletOrder: Response is " . ACTINIC::EncodeJson(\%hJsonResponse));
	return ($::SUCCESS);
	}

#######################################################
#
# GetContactDetailsFromPaymentRequest - get the billing
#						and shipping contact details from the
#						payment request
#
#	Input:	0 - Payment Request hash
#				1 - hash to receive the wallet contact
#
#	Returns:	0 - status $::SUCCESS if successful
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub GetContactDetailsFromPaymentRequest
	{
	my $pPaymentRequest = shift;
	my $pWalletBillingContact = shift;
	#
	# Check if the cart has tangible products
	#
	my $bCartHasTangibleGoods = ACTINIC::CartHasTangibleGoods();
	#
	# Editable shipping address by default
	#
	my $bFixedDeliveryAddress = $::FALSE;
	#
	# No editable shipping address if registered buyer
	# unless they are allowed to enter a new address
	#
	my $sDigest = $ACTINIC::B2B->Get('UserDigest');
	my ($Status, $sMessage, $pBuyer) = ACTINIC::GetBuyer($sDigest, ACTINIC::GetPath()); # look up the buyer
	if ($sDigest &&										# if this is a customer account
		$pBuyer->{'DeliveryAddressRule'} != 2)		# and they can't enter a delivery address
		{
		$bFixedDeliveryAddress = $::TRUE;
		}
	my ($bUsesPaymentBillAddress, $bUsesPaymentShippAddress) = ($::FALSE, $::FALSE);
	#
	# We need to preserve the billing address which came from Apple Pay
	#
	GetContactFromPaymentRequest($pPaymentRequest, 'billingAddress', $pWalletBillingContact);
	#
	# Note the Wallet Billing country data
	#
	($::g_LocationInfo{'WALLET_COUNTRY_CODE'}, $pWalletBillingContact->{'COUNTRY'}) =
			GetLocationCountry($pPaymentRequest->{'customerInfo'}{'billingAddress'}{'country'});
	#
	# We ignore the payment request billing address for registered customers
	# instead we shall retain the account invoice address in the session file
	#
	if (!$ACTINIC::B2B->Get('UserDigest'))
		{
		$bUsesPaymentBillAddress = $::TRUE;
		GetContactFromPaymentRequest($pPaymentRequest, 'billingAddress', \%::g_BillContact);
		($::g_LocationInfo{'INVOICE_COUNTRY_CODE'}, $::g_BillContact{'COUNTRY'}) =
				GetLocationCountry($pPaymentRequest->{'customerInfo'}{'billingAddress'}{'country'});
		}
	#
	# Get the delivery address if not using a fixed delivery address
	#
	if (!$bFixedDeliveryAddress)
		{
		#
		# Find our shipping address
		#
		if ($bCartHasTangibleGoods)
			{
			#
			# We have tangible products and are not using a fixed address
			# so we need the shiping address from Apple Pay
			#
			$bUsesPaymentShippAddress = $::TRUE;
			if ($pPaymentRequest->{'customerInfo'}{'sameShippingAndBilling'})
				{
				#
				# AP returned same address for billing and
				# shipping so we have to use the billing node
				#
				GetContactFromPaymentRequest($pPaymentRequest, 'billingAddress', \%::g_ShipContact);
				($::g_LocationInfo{'DELIVERY_COUNTRY_CODE'}, $::g_ShipContact{'COUNTRY'}) =
						GetLocationCountry($pPaymentRequest->{'customerInfo'}{'billingAddress'}{'country'});
				}
			else
				{
				GetContactFromPaymentRequest($pPaymentRequest, 'shippingAddress', \%::g_ShipContact);
				($::g_LocationInfo{'DELIVERY_COUNTRY_CODE'}, $::g_ShipContact{'COUNTRY'}) =
						GetLocationCountry($pPaymentRequest->{'customerInfo'}{'shippingAddress'}{'country'});
				#
				# ClearAccept don't return separate name fields for shipping
				# so copy the bill contact name fields to shipping contact
				#
				$::g_ShipContact{'FIRSTNAME'} = $::g_BillContact{'FIRSTNAME'};
				$::g_ShipContact{'LASTNAME'} = $::g_BillContact{'LASTNAME'};
				$::g_ShipContact{'NAME'} = $::g_BillContact{'NAME'};
				}
			#
			# Flll in missing shipping contact fields from billing contact
			#
			if (length $::g_ShipContact{'EMAIL'} < 1)
				{
				$::g_ShipContact{'EMAIL'} = $::g_BillContact{'EMAIL'};
				}
			if (length $::g_ShipContact{'PHONE'} < 1)
				{
				$::g_ShipContact{'PHONE'} = $::g_BillContact{'PHONE'};
				}
			}
		else
			{
			#
			# Copy the billing address to the shipping address
			# if no tangible products
			#
			foreach (@ActinicOrder::arrAddressKeys)
				{
				$::g_ShipContact{$_} = $::g_BillContact{$_};
				}
			}
		}
	#
	# Set the location info region fields
	#
	$::g_LocationInfo{'INVOICE_REGION_CODE'}	= ActinicOrder::GetSellerDeckRegion($::g_LocationInfo{'INVOICE_COUNTRY_CODE'}, $::g_BillContact{'ADDRESS4'}, $::g_BillContact{'POSTALCODE'});
	$::g_LocationInfo{'DELIVERY_REGION_CODE'} = ActinicOrder::GetSellerDeckRegion($::g_LocationInfo{'DELIVERY_COUNTRY_CODE'}, $::g_ShipContact{'ADDRESS4'}, $::g_ShipContact{'POSTALCODE'});
	#
	# Both addresses from payment request so use PR separate ship flag
	#
	if ($bUsesPaymentShippAddress &&
		 $bUsesPaymentBillAddress)
		{
		$::g_BillContact{'SEPARATE'} = $::g_LocationInfo{'SEPARATESHIP'} = ($pPaymentRequest->{'customerInfo'}{'sameShippingAndBilling'}) ? $::FALSE : $::TRUE;
		}
	else
		{
		#
		# Only one address from payment request so
		# assume different unless no tangible products
		#
		$::g_BillContact{'SEPARATE'} = $::g_LocationInfo{'SEPARATESHIP'} = ($bCartHasTangibleGoods) ? $::TRUE : $::FALSE;
		}
	#
	# Address fields not returned yet known
	#
	$::g_ShipContact{'PRIVACY'} 		= $::TRUE;
	$::g_BillContact{'PRIVACY'} 		= $::TRUE;
	$::g_BillContact{'MOVING'} 		= $::FALSE;
	$::g_BillContact{'REMEMBERME'}	= $::FALSE;

	if ($::g_LocationInfo{'DELIVERY_REGION_CODE'} eq $ActinicOrder::UNDEFINED_REGION)
		{
		#
		# Check if the cart has tangible products
		#
		my $bCartHasTangibleGoods = ACTINIC::CartHasTangibleGoods();
		#
		# Delivery region is undefined, check if need it
		#
		if ($bCartHasTangibleGoods &&
			 ActinicOrder::StateRequiredForValidation('Delivery', $::g_LocationInfo{'DELIVERY_REGION_CODE'}))
			{
			return ($::FAILURE, ACTINIC::GetPhrase(-1, 196));
			}
		}

	if ($::g_LocationInfo{'INVOICE_REGION_CODE'} eq $ActinicOrder::UNDEFINED_REGION)
		{
		#
		# Invoice region is undefined, check if need it
		#
		if (ActinicOrder::StateRequiredForValidation('Invoice', $::g_LocationInfo{'INVOICE_REGION_CODE'}))
			{
			return ($::FAILURE, ACTINIC::GetPhrase(-1, 196));
			}
		}
	#
	# Check address field lengths
	#
	my $sError = main::CheckBothAddressesFieldLengths();
	if (length $sError)
		{
		return($::FAILURE, $sError);
		}
	ActinicOrder::NormaliseAddressLocation('Invoice');
	ActinicOrder::NormaliseAddressLocation('Delivery');
	}

#######################################################
#
# GetContactFromPaymentRequest - get a contact
#							from the payment request
#
#	Input:	0 - Pointer to a payment Request hash
#				2 - Name of node to read (billing/shipping)
#				1 - hash to receive the contact details
#
#######################################################

sub GetContactFromPaymentRequest
	{
	my $pPaymentRequest = shift;
	my $sAddressNode = shift;
	my $pResponse = shift;
	#
	# Get the billing address details from the payment request
	#
	if ($$::g_pSetupBlob{'SHOPPER_NAME_HANDLING_MODE'} == 1) # first name/ last name handling
		{
		$pResponse->{'FIRSTNAME'}	= $pPaymentRequest->{'customerInfo'}{'firstName'};
		$pResponse->{'LASTNAME'}	= $pPaymentRequest->{'customerInfo'}{'lastName'};
		}
	$pResponse->{'NAME'}		= $pPaymentRequest->{'customerInfo'}{'firstName'} . ' ' .
											  $pPaymentRequest->{'customerInfo'}{'lastName'};
	$pResponse->{'NAME'}		=~ s/(.{0,$::g_pFieldSizes->{'NAME'}}).*/$1/;	# trim to max field size
	$pResponse->{'EMAIL'}			= $pPaymentRequest->{'customerInfo'}{'email'};
	$pResponse->{'PHONE'}			= $pPaymentRequest->{'customerInfo'}{'phoneNumber'};
	$pResponse->{'ADDRESS1'}		= $pPaymentRequest->{'customerInfo'}{$sAddressNode}{'addressLine1'};
	$pResponse->{'ADDRESS2'}		= $pPaymentRequest->{'customerInfo'}{$sAddressNode}{'addressLine2'};
	$pResponse->{'ADDRESS3'}		= $pPaymentRequest->{'customerInfo'}{$sAddressNode}{'city'};
	$pResponse->{'ADDRESS4'}		= $pPaymentRequest->{'customerInfo'}{$sAddressNode}{'county'};
	$pResponse->{'POSTALCODE'}		= $pPaymentRequest->{'customerInfo'}{$sAddressNode}{'postalCode'};
	}

#######################################################
#
# GetLocationCountry - get SDD country code and country name
#
#	Inout:	0 - country code
#
#	Returns:	0 - country code
#				1 - country name
#
#######################################################

sub GetLocationCountry
	{
	my $sCountryCode = shift;
	#
	# Change GB country code to UK
	#
	$sCountryCode =~ s/^GB$/UK/;
	#
	# Return the country code and name
	# Country code is returnd as name if lookup fails
	#
	return ($sCountryCode, (ACTINIC::GetCountryName($sCountryCode) ne "") ?
									ACTINIC::GetCountryName($sCountryCode) : $sCountryCode);
	}

#######################################################
#
# RecordPaymentRequest - record the payment request
#								 we do this so that we can later
#								 recover if the confirm fails
#
#	Inout:	0 - hash of payment request
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub RecordPaymentRequest
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in RecordPaymentRequest ($#_)", __LINE__, __FILE__);
	my $hPaymentRequest = shift;

	if ('confirmed' eq $hPaymentRequest->{'status'})
		{
		#
		# Unexpected Payment Request status
		#
		RecordErrors(sprintf("ClearAccept::RecordPaymentRequest: Unexpected Payment Request status [%s])", $hPaymentRequest->{'status'}));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	my $nAmount = $hPaymentRequest->{'amount'};
	my $sTimeStamp = ACTINIC::GetActinicDateFromISO8601Date(ACTINIC::GetISO8601Date());	# time now (no creation date on payment requests)
	LogData(sprintf("ClearAccept::RecordPaymentRequest: %s %s", $hPaymentRequest->{'paymentRequestId'}, $sTimeStamp));
	#
	# Make sure the currency is as expected
	#
	if (!IsSameCurrency($hPaymentRequest->{'currency'}))
		{
		my $sError = sprintf("Authorisation not recorded because of currency mismatch. Received %s but expected %s for order %s", $hPaymentRequest->{'currency'}, GetOrderCurrencyCode(), $::g_PaymentInfo{'ORDERNUMBER'});
		RecordErrors('ClearAccept::RecordPaymentRequest: ' . $sError);
		#
		# A payment has been received but for the wrong currency
		# Do not record the transaction
		#
		return($::SUCCESS, '');
		}

	my $sAction = $::g_InputHash{ACTION};			# the auth function uses this so we need to override but create a backup first
	$::g_InputHash{'ACTION'} = sprintf("AUTHORIZE_%d", $::PAYMENT_CLEARACCEPT);
	$::g_InputHash{'ON'} = $::g_PaymentInfo{'ORDERNUMBER'};
	$::g_InputHash{'ORDERNUMBER'} = $::g_PaymentInfo{'ORDERNUMBER'};
	$::g_InputHash{'TM'} = $ClearAccept::TESTMODE;
	$::g_InputHash{'PA'} = $ClearAccept::IMMEDIATE_CAPTURE ? $::FALSE : $::TRUE;
	$::g_InputHash{'AM'} = $nAmount;

	my $sParams;
	$sParams .= sprintf("ON=%s", $::g_PaymentInfo{'ORDERNUMBER'});	# ON must be first parameter
	$sParams .= sprintf("&PS=%s", $::PSP_STATUS_OCCPending);
	$sParams .= sprintf("&TM=%s", $ClearAccept::TESTMODE);
	$sParams .= sprintf("&AM=%s", $nAmount);
	$sParams .= sprintf("&TX=%s", $hPaymentRequest->{'paymentRequestId'});
	$sParams .= sprintf("&DT=%s", ACTINIC::EncodeText2($sTimeStamp, $::FALSE));	# time stamp needs to be encoded
	$sParams .= sprintf("&PA=%s", $ClearAccept::IMMEDIATE_CAPTURE ? $::FALSE : $::TRUE);
	#
	# Create the signature, must append the & as SDD expects it this way
	#
	$sParams .= '&';
	my $sSignature = ACTINIC::GetMD5Hash($sParams);
	$sParams .= sprintf("SN=%s", $sSignature);
	#
	# Record the authorization and submits to MailChimp if required
	#
	LogData(sprintf("ClearAccept::RecordPaymentRequest: calling RecordAuthorization with %s", $sParams));
	my $sError = main::RecordAuthorization(\$sParams);
	$::g_InputHash{ACTION} = $sAction;				# restore the original action
	if (length $sError)
		{
		#
		# Record any error to error.err
		#
		RecordErrors(sprintf("ClearAccept::RecordPaymentRequest: RecordAuthorization returned  %s", $sError));
		}
	else
		{
		LogData(sprintf("ClearAccept::RecordPaymentRequest: finished successfully"));
		}

	return ($::SUCCESS, '');
	}

#######################################################
#
# VerifyAndRecordConfirmation - verify the payment request
#											and record authorisation
#
#	Inout:	0 - hash of confirmation response
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub VerifyAndRecordConfirmation
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in VerifyAndRecordConfirmation ($#_)", __LINE__, __FILE__);
	my $hPaymentRequest = shift;

	my ($nStatus, $sError, $hResponse);
	if ('confirmed' ne $hPaymentRequest->{'status'})
		{
		#
		# Cannot confirm the Payment Request
		#
		RecordErrors(sprintf("ClearAccept::VerifyAndRecordConfirmation: Payment Request (status [%s]) has not been confirmed", $hPaymentRequest->{'status'}));
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	if ((!defined $hPaymentRequest->{'transactions'}) ||
		 (ref($hPaymentRequest->{'transactions'}) ne 'ARRAY'))
		{
		#
		# No transactions for the Payment Request
		#
		RecordErrors("ClearAccept::VerifyAndRecordConfirmation: No transactions array for the Payment Request");
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	my $aTransactions = $hPaymentRequest->{'transactions'};
	#
	# There should be an Authorisation transaction and, if pay immediately, a Capture transaction
	# The Capture just confirms the capture. Most of the authorisation details will be on the Authorisation transaction
	#
	my $bPreAuthorise = $::TRUE;
	my ($nIdx, $nAuthorisationIdx, $nCaptureIdx) = (-1, -1, -1);
	my $sError;
	foreach my $hTransaction (@{$aTransactions})
		{
		$nIdx++;
		if ('Capture' eq $hTransaction->{'type'})
			{
			if ($nCaptureIdx > -1)
				{
				#
				# Unexpectedly found more than 1 Capture transactions
				# Report and continue
				#
				RecordErrors("ClearAccept::VerifyAndRecordConfirmation: Unexpectedly found more than 1 Capture transactions");
				}
			$nCaptureIdx = $nIdx;
			$bPreAuthorise = $::FALSE;
			if ('Pending' eq $hTransaction->{'status'})
				{
				#
				# Pending transaction, should not happen
				#
				RecordErrors(sprintf("ClearAccept::VerifyAndRecordConfirmation: Pending Capture transaction PRid:%s TXid:%s", $hPaymentRequest->{'paymentRequestId'}, $hTransaction->{'transactionId'}));
				$sError = sprintf($ClearAccept::PENDINGMSG, $::g_PaymentInfo{'ORDERNUMBER'});
				}
			elsif ('Declined' eq $hTransaction->{'status'})
				{
				#
				# Declined transaction, cannot continue
				#
				LogData(sprintf("ClearAccept::VerifyAndRecordConfirmation: Declined Capture transaction PRid:%s Reason:%s", $hPaymentRequest->{'paymentRequestId'}, $hTransaction->{'declineReason'}));
				$sError = $ClearAccept::DECLINEMSG;
				}
			elsif ('Approved' ne $hTransaction->{'status'})
				{
				#
				# Unrecognised transaction status
				#
				RecordErrors(sprintf("ClearAccept::VerifyAndRecordConfirmation: Unrecognised transaction PRid:%s Reason:%s", $hPaymentRequest->{'paymentRequestId'}, $hTransaction->{'status'}));
				return ($::FAILURE, $ClearAccept::ERRORMSG);
				}
			}
		if ('Authorisation' eq $hTransaction->{'type'})
			{
			if ($nAuthorisationIdx > -1)
				{
				#
				# Unexpectedly found more than 1 Authorisation transactions
				# Report and continue
				#
				RecordErrors("ClearAccept::VerifyAndRecordConfirmation: Unexpectedly found more than 1 Authorisation transactions");
				}
			if ('Pending' eq $hTransaction->{'status'})
				{
				#
				# Pending transaction, should not happen
				#
				RecordErrors(sprintf("ClearAccept::VerifyAndRecordConfirmation: Pending Authroisation transaction PRid:%s TXid:%s", $hPaymentRequest->{'paymentRequestId'}, $hTransaction->{'transactionId'}));
				$sError = sprintf($ClearAccept::PENDINGMSG, $::g_PaymentInfo{'ORDERNUMBER'});
				}
			elsif ('Declined' eq $hTransaction->{'status'})
				{
				#
				# Declined transaction, cannot continue
				# Declined reason should not be shown to the buyer as it may aid fraudsters
				#
				LogData(sprintf("ClearAccept::VerifyAndRecordConfirmation: Declined Authorisation transaction PRid:%s Reason:%s", $hPaymentRequest->{'paymentRequestId'}, $hTransaction->{'declineReason'}));
				$sError = $ClearAccept::DECLINEMSG;
				}
			elsif ('Approved' ne $hTransaction->{'status'})
				{
				#
				# Unrecognised transaction status
				#
				RecordErrors(sprintf("ClearAccept::VerifyAndRecordConfirmation: Unrecognised transaction PRid:%s Reason:%s", $hPaymentRequest->{'paymentRequestId'}, $hTransaction->{'status'}));
				return ($::FAILURE, $ClearAccept::ERRORMSG);
				}
			$nAuthorisationIdx = $nIdx;
			}
		}
	$nIdx = $bPreAuthorise ? $nAuthorisationIdx : $nCaptureIdx;
	my $nAmount = @{$aTransactions}[$nIdx]->{'amount'};
	my $sCaptureId = $bPreAuthorise ? '' : @{$aTransactions}[$nCaptureIdx]->{'transactionId'};
	my $sTimeStamp = ACTINIC::GetActinicDateFromISO8601Date(@{$aTransactions}[$nIdx]->{'createdDateTime'});
	RecordAuthorization(	$bPreAuthorise,
								$nAmount,
								$sCaptureId,
								$sTimeStamp,
								$hPaymentRequest,
								$nAuthorisationIdx);
	if (length $sError > 0)
		{
		return ($::FAILURE, $sError);
		}
	return ($::SUCCESS, '');
	}

######################################################################
#
# GetPaymentRequest	- gets a Payment Request
#								may be an existing or new request
#
#	Input:	0 - allow create request if not found (default false)
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#				2 - hash of Payment Request if status is $::SUCCESS
#
######################################################################

sub GetPaymentRequest
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in GetPaymentRequest ($#_)", __LINE__, __FILE__);
	my $bAllowCreate = shift;
	if (!defined $bAllowCreate)
		{
		$bAllowCreate = $::FALSE;
		}

	my ($nStatus, $sError, $sPaymentRequestId, $hPaymentRequest);
	#
	# Check if we already have an appropriate Payment Request
	#
	$sPaymentRequestId = $::Session->GetProviderParam($::PAYMENT_CLEARACCEPT, $::CLEARACCEPT_PAYMENT_ID);
	if (length $sPaymentRequestId < 1)
		{
		#
		# No previous ID so create a new request
		#
		return (CreatePaymentRequest($bAllowCreate));
		}
	#
	# We have a previous Payment Request, check it is still usable
	#
	($nStatus, $sError, $hPaymentRequest) = ReadPaymentRequest($sPaymentRequestId);
	if ($nStatus != $::SUCCESS)
		{
		#
		# Cannot read previous Payment Request so create a new request
		#
		return (CreatePaymentRequest($bAllowCreate));
		}
	LogData(sprintf('ClearAccept::GetPaymentRequest: Found previous Payment Request: %s Status: %s', $sPaymentRequestId, $hPaymentRequest->{'status'}));
	return ($::SUCCESS, '', $hPaymentRequest);
	}

######################################################################
#
# CreatePaymentRequest	- creates a new Payment Request
#
#	Input:	0 - allow create request
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#				2 - hash of Payment Request if status is $::SUCCESS
#
######################################################################

sub CreatePaymentRequest
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in CreatePaymentRequest ($#_)", __LINE__, __FILE__);
	my $bAllowCreate = shift;
	if (!$bAllowCreate)
		{
		return($::FAILURE, 'ClearAccept::CreatePaymentRequest: Payment request creation not allowed at this time');
		}
	my %hContacts = {};
	$hContacts{'BillContact'} = \%::g_BillContact;
	$hContacts{'ShipContact'} = \%::g_ShipContact;
	$hContacts{'SeparateShipAddress'} = $::g_BillContact{'SEPARATE'};
	$hContacts{'INVOICE_COUNTRY_CODE'} = $::g_LocationInfo{'INVOICE_COUNTRY_CODE'};
	$hContacts{'DELIVERY_COUNTRY_CODE'} = $::g_LocationInfo{'DELIVERY_COUNTRY_CODE'};
	my (%hParams) = BuildPaymentRequestParams(\%hContacts);
	#
	# Submit the request
	#
	my ($nStatus, $sError, $hPaymentRequest) = SendApiRequest($ClearAccept::TRANSACT, 'transact/payment-requests', 'POST', \%hParams);
	if ($nStatus == $::SUCCESS)
		{
		$::Session->SetProviderParam($::PAYMENT_CLEARACCEPT, $::CLEARACCEPT_PAYMENT_ID, $hPaymentRequest->{'paymentRequestId'});
		}
	return($nStatus, $sError, $hPaymentRequest);
	}

######################################################################
#
# BuildPaymentRequestParams	- builds the prams for a Payment Request
#
#	Input:	1 - hash of contacts
#
#	Returns:	0 - hash of payment request parameters
#
######################################################################

sub BuildPaymentRequestParams
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in BuildPaymentRequestParams ($#_)", __LINE__, __FILE__);
	my $pContacts = shift;
	my (%hParams) =
		(
		'merchantSiteId' => $ClearAccept::PSP_MERCHANT_SITE_ID,
		'amount' => ActinicOrder::GetOrderTotal($::TRUE),
		'currency' => GetOrderCurrencyCode(),
		'channel' => 'ECOM',
		'confirm' => $::JSONFALSE,
		'capture' => $ClearAccept::IMMEDIATE_CAPTURE ? $::JSONTRUE : $::JSONFALSE
		);
	AddCustomerInfo(\%hParams, $pContacts);
	AddMerchantReferences(\%hParams);
	AddAdditionalData(\%hParams);
	return (%hParams);
	}

######################################################################
#
# UpdatePaymentRequest	- update the Payment Request with a permanent token
#
#	Input:	0 - payment request ID
#				1 - hash of contacts
#				2 - permanent card token (optional)
#				3 - allow card to be saved (optional)
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#				2 - hash of Payment Request if status is $::SUCCESS
#
######################################################################

sub UpdatePaymentRequest
	{
#? ACTINIC::ASSERT($#_ > 0, "Invalid argument count in UpdatePaymentRequest ($#_)", __LINE__, __FILE__);
	my $sPaymentRequestId = shift;
	my $pContacts = shift;
	my $sCardToken = shift;
	my $bPersist = shift;

	if (!defined $sCardToken)
		{
		$sCardToken = "";
		}
	if (!defined $bPersist)
		{
		$bPersist = $::FALSE;
		}
	my (%hParams) = BuildPaymentRequestParams($pContacts);
	if ($bPersist)
		{
		$hParams{'paymentMethod'}{'persist'} = $::JSONTRUE;	# documented but doesn't work
		}
	if (length $sCardToken > 0)
		{
		$hParams{'paymentMethod'}{'token'} = $sCardToken;
		}
	#
	# Submit the request
	#
	return(SendApiRequest($ClearAccept::TRANSACT, 'transact/payment-requests/' . $sPaymentRequestId, 'PUT', \%hParams));
	}

######################################################################
#
# ReadPaymentRequest	- read a payment request
#
# Input		0 -	payment request ID
#
# Returns	0 -	payment request hash
#
######################################################################

sub ReadPaymentRequest
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in ReadPaymentRequest ($#_)", __LINE__, __FILE__);
	my $sPaymentRequestId = shift;
	#
	# Submit the request
	#
	return(SendApiRequest($ClearAccept::TRANSACT, 'transact/payment-requests/' . $sPaymentRequestId, 'GET'));
	}

######################################################################
#
# ConfirmPaymentRequest	- confirm a payment request
#
# Input		0 -	Payment Request ID
#				1 -	token (card or cvv)
#				2 -	persist token if true
#
# Returns	0 -	payment request hash
#
######################################################################

sub ConfirmPaymentRequest
	{
#? ACTINIC::ASSERT($#_ == 1, "Invalid argument count in ConfirmPaymentRequest ($#_)", __LINE__, __FILE__);
	my $sPaymentRequestId = shift;
	my $sToken = shift;
	my $bPersist = shift;

	if (!defined $sToken)
		{
		$sToken = "";
		}
	if (!defined $bPersist)
		{
		$bPersist = $::FALSE;
		}

	my (%hParams) =
		(
		'merchantSiteId' => $ClearAccept::PSP_MERCHANT_SITE_ID,
		);

		$hParams{'paymentMethod'}{'persist'} = $bPersist ? $::JSONTRUE : $::JSONFALSE;

	if (length $sToken > 0)
		{
		my $sKey = ($sToken =~ /^cvvtemp_/i) ? 'cvvToken' : 'token';
		$hParams{'paymentMethod'}{$sKey} = $sToken;
		}

	my $nRetries = $ClearAccept::HTTP_MAX_RETRIES;

	my ($nStatus, $sError, $hResponse, $nResponseStatus);

	while ($nRetries > 0)
		{
		#
		# Submit the request, we won't retry a confirm that fails with a 5xx error
		#
		($nStatus, $sError, $hResponse, $nResponseStatus) = SendApiRequest($ClearAccept::TRANSACT, 'transact/payment-requests/' . $sPaymentRequestId . '/confirm', 'POST', \%hParams, $::FALSE);
	
		my $b5xxError = (defined $nResponseStatus && $nResponseStatus > 499) ? $::TRUE : $::FALSE;
	
		if (($nStatus == $::SUCCESS) ||
			 !$b5xxError)
			{
			#
			# Return if success or not a 5xx error
			#
			$sError = $nStatus == $::SUCCESS ? '' : $ClearAccept::ERRORMSG;
			return ($nStatus, $sError, $hResponse, $nResponseStatus);
			}
		#
		# The confirm may have succeeded but we have not been able to get the response
		# so we shall attepmt to read the payment request to see what happened
		#
		($nStatus, $sError, $hResponse) = ReadPaymentRequest($sPaymentRequestId);
		if ($nStatus != $::SUCCESS)
			{
			#
			# We could not read the payment request even after several retries
			#
			return ($nStatus, sprintf($ClearAccept::UNKNOWNSTATEMSG, $::g_PaymentInfo{'ORDERNUMBER'}));
			}
		if ('confirmed' eq $hResponse->{'status'})
			{
			#
			# The confirm succeeded so we can continue normally
			#
			return ($::SUCCESS, '', $hResponse);
			}
		$nRetries--;
		if ($nRetries > 0)
			{
			sleep $ClearAccept::HTTP_RETRY_INTERVAL;	# wait before retry
			LogData('ClearAccept::ConfirmPaymentRequest: Retrying confirm');
			}
		}
	#
	# Return whatever was the last response
	#
	$sError = $nStatus == $::SUCCESS ? '' : $ClearAccept::ERRORMSG;
	return ($nStatus, $sError, $hResponse);
	}

#######################################################
#
# GetOrderCurrencyCode - Get the order currency code
#
#	Returns:	0 - the currency code
#
#######################################################

sub GetOrderCurrencyCode
	{
	#
	# Get the order currency
	#
	return ($$::g_pCatalogBlob{SINTLSYMBOLS});
	}

#######################################################
#
# AddCustomerInfo - add Customer Info to hash
#
#	Input:	0 - param hash
#				1 - hash of contacts
#
#######################################################

sub AddCustomerInfo
	{
#? ACTINIC::ASSERT($#_ == 1, "Invalid argument count in AddCustomerInfo ($#_)", __LINE__, __FILE__);
	my ($hParams) = shift;
	my $pContacts = shift;

	my ($sCountryCode, $sStateProvince);
	#
	# Get the ISO billing country code
	#
	$sCountryCode = ActinicLocations::GetISOCountryCode($pContacts->{'INVOICE_COUNTRY_CODE'});
	#
	# Get the billing state/province
	# use the region code (state code) for United States
	#
	$sStateProvince = ($sCountryCode eq 'US') ? ActinicLocations::GetISORegionCode($pContacts->{'BillContact'}{'ADDRESS4'}) : $pContacts->{'BillContact'}{'ADDRESS4'};
	#
	# Bill to name must not exceed 45 characters
	# Truncate and if at all possible preserve the last name
	# i.e. do not simply truncate
	#
	my $sBillToName = GetFullName($pContacts->{'BillContact'});
	if (length $sBillToName > 45)
		{
		if ($sBillToName =~ /(\S+)(\s+)(.*)/)
			{
			#
			# Split the name into words
			#
			my @aWords = split(' ', $sBillToName);
			my $nWords = @aWords;
			#
			# Concatenate the first and last words
			#
			$sBillToName = sprintf("%s %s", $aWords[0], $aWords[$nWords - 1]);
			if (length $sBillToName > 45)
				{
				#
				# Name still longer than 45 characters and has no spaces so take only first initial and last word
				#
				$sBillToName = sprintf("%s. %s", substr($aWords[0], 0, 1), $aWords[$nWords - 1]);
				}
			}
		else
			{
			#
			# Name longer than 45 characters and has no spaces so take first 45 characters
			#
			$sBillToName = substr($hParams->{'paymentMethod'}{'accountHolderName'}, 0, 45);
			}
		}
	$hParams->{'paymentMethod'}{'accountHolderName'} =$sBillToName;
	$hParams->{'paymentMethod'}{'billingDetails'}{'name'} = $sBillToName;
	$hParams->{'paymentMethod'}{'billingDetails'}{'email'} = $pContacts->{'BillContact'}{'EMAIL'};
	$hParams->{'paymentMethod'}{'billingDetails'}{'address'}{'addressLine1'} = $pContacts->{'BillContact'}{'ADDRESS1'};
	$hParams->{'paymentMethod'}{'billingDetails'}{'address'}{'addressLine2'} = $pContacts->{'BillContact'}{'ADDRESS2'};
	$hParams->{'paymentMethod'}{'billingDetails'}{'address'}{'city'} = $pContacts->{'BillContact'}{'ADDRESS3'};
	$hParams->{'paymentMethod'}{'billingDetails'}{'address'}{'county'} = $sStateProvince;
	$hParams->{'paymentMethod'}{'billingDetails'}{'address'}{'country'} = $sCountryCode;
	$hParams->{'paymentMethod'}{'billingDetails'}{'address'}{'postalCode'} = $pContacts->{'BillContact'}{'POSTALCODE'};

	if ($$::g_pSetupBlob{'SHOPPER_NAME_HANDLING_MODE'} == 1) # first name/ last name handling
		{
		$hParams->{'customerInfo'}{'firstName'} = $pContacts->{'BillContact'}{'FIRSTNAME'};
		$hParams->{'customerInfo'}{'lastName'} = $pContacts->{'BillContact'}{'LASTNAME'};
		}
	else
		{
		($hParams->{'customerInfo'}{'firstName'} , $hParams->{'customerInfo'}{'lastName'}) = ActinicOrder::SplitCustomerName(GetFullName($pContacts->{'BillContact'}));
		}
	$hParams->{'customerInfo'}{'email'} = $pContacts->{'BillContact'}{'EMAIL'};

	if ($pContacts->{'SeparateShipAddress'})
		{
		#
		# Get the ISO shipping country code
		#
		$sCountryCode = ActinicLocations::GetISOCountryCode($pContacts->{'DELIVERY_COUNTRY_CODE'});
		#
		# Get the shipping state/province
		# use the region code (state code) for United States
		#
		$sStateProvince = ($sCountryCode eq 'US') ? ActinicLocations::GetISORegionCode($pContacts->{'ShipContact'}{'ADDRESS4'}) : $pContacts->{'ShipContact'}{'ADDRESS4'};

		$hParams->{'customerInfo'}{'sameShippingAndBilling'} = $::JSONFALSE;
		$hParams->{'customerInfo'}{'shippingAddress'}{'addressLine1'} = $pContacts->{'ShipContact'}{'ADDRESS1'};
		$hParams->{'customerInfo'}{'shippingAddress'}{'addressLine2'} = $pContacts->{'ShipContact'}{'ADDRESS2'};
		$hParams->{'customerInfo'}{'shippingAddress'}{'city'} = $pContacts->{'ShipContact'}{'ADDRESS3'};
		$hParams->{'customerInfo'}{'shippingAddress'}{'county'} = $sStateProvince;
		$hParams->{'customerInfo'}{'shippingAddress'}{'country'} = $sCountryCode;
		$hParams->{'customerInfo'}{'shippingAddress'}{'postalCode'} = $pContacts->{'ShipContact'}{'POSTALCODE'};
		}
	else
		{
		$hParams->{'customerInfo'}{'sameShippingAndBilling'} = $::JSONTRUE;
		}
	}

#######################################################
#
# AddMerchantReferences - add Merchant References to hash
#
#	Input		0 - param hash
#
#######################################################

sub AddMerchantReferences
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in AddMerchantReferences ($#_)", __LINE__, __FILE__);
	my ($hParams) = shift;
#? ACTINIC::ASSERT(length $::g_PaymentInfo{'ORDERNUMBER'} > 0, "Missing order number in GetMerchantReferences", __LINE__, __FILE__);

	$hParams->{'merchantReferences'}{'transactionReference'} = $::g_PaymentInfo{'ORDERNUMBER'};
	}

#######################################################
#
# AddAdditionalData - add Additional Data to hash
#
#	Input		0 - param hash
#
#######################################################

sub AddAdditionalData
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in AddAdditionalData ($#_)", __LINE__, __FILE__);
	my ($hParams) = shift;
	$hParams->{'additionalData'}{'transactionPurpose'} = 'GoodsServicePurchase';
	}

#######################################################
#
# GetFullName - get full name from an address
#
#	Input		0 - address hash
#
#	Returns:	0 - Returns full name
#
#######################################################

sub GetFullName
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in GetFullName ($#_)", __LINE__, __FILE__);
	my $hAddress = shift;
#? ACTINIC::ASSERT(length $hAddress{'NAME'} > 0, "Warning: missing full name in GetFullName", __LINE__, __FILE__);
	return ($hAddress->{'NAME'});
	}

######################################################################
#
# GetFieldToken	- creates a new Field Token
#
#	Input		0 - Payment request ID
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#				2 - hash of Field Token if status is $::SUCCESS
#
######################################################################

sub GetFieldToken
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in GetFieldToken ($#_)", __LINE__, __FILE__);
	my $sPaymentRequestId = shift;

	my (%hParams) =
		(
		'paymentRequestId' => $sPaymentRequestId
		);
	return(SendApiRequest($ClearAccept::TRANSACT, 'transact/field-tokens', 'POST', \%hParams));
	}

#######################################################
#
# RecordAuthorization - implements ClearAccept specific
#									payment authorization
#
# Params:	0 - Pre-Authorised (true/false)
#				1 - Authorised/Captured amount
#				2 - Capture ID, only if captured
#				3 - Creation time stamp
#				4 - full payment request
#				5 - index of authorisation transaction
#
# Returns:	0 - $::SUCCESS or $::FAILURE
#				1 - undef or error message
#
#######################################################

sub RecordAuthorization
	{
#? ACTINIC::ASSERT($#_ == 5, "Invalid argument count in RecordAuthorization ($#_)", __LINE__, __FILE__);
	my ($bPreAuthorise, $nAmount, $sCaptureId, $sTimeStamp, $hPaymentRequest, $nAuthorisationIdx) = @_;

	my $hTransaction = $hPaymentRequest->{'transactions'}[$nAuthorisationIdx];

	LogData(sprintf("ClearAccept::RecordAuthorization: %s %s", $hPaymentRequest->{'paymentRequestId'}, $sTimeStamp));

	#
	# Make sure the currency is as expected
	#
	if (!IsSameCurrency($hTransaction->{'currency'}))
		{
		my $sError = sprintf("Authorisation not recorded because of currency mismatch. Received %s but expected %s for order %s", $hTransaction->{'currency'}, GetOrderCurrencyCode(), $::g_PaymentInfo{'ORDERNUMBER'});
		RecordErrors('ClearAccept::RecordAuthorization: ' . $sError);
		#
		# A payment has been received but for the wrong currency
		# Do not record the transaction
		#
		return($::SUCCESS, '');
		}

	my $sAction = $::g_InputHash{ACTION};			# the auth function uses this so we need to override but create a backup first
	$::g_InputHash{'ACTION'} = sprintf("AUTHORIZE_%d", $::PAYMENT_CLEARACCEPT);
	$::g_InputHash{'ON'} = $::g_PaymentInfo{'ORDERNUMBER'};
	$::g_InputHash{'ORDERNUMBER'} = $::g_PaymentInfo{'ORDERNUMBER'};
	$::g_InputHash{'TM'} = $ClearAccept::TESTMODE;
	$::g_InputHash{'PA'} = $bPreAuthorise;
	$::g_InputHash{'AM'} = $nAmount;

	my $nPaymentStatus = $bPreAuthorise ? $::PSP_STATUS_PreAuth : $::PSP_STATUS_Accepted;
	if ('Declined' eq $hTransaction->{'status'})
		{
		$nPaymentStatus = $::PSP_STATUS_Rejected;
		}
	elsif ('Pending' eq $hTransaction->{'status'})
		{
		$nPaymentStatus = $::PSP_STATUS_OCCPending;
		}
	my $sParams;
	$sParams .= sprintf("ON=%s", $::g_PaymentInfo{'ORDERNUMBER'});	# ON must be first parameter
	$sParams .= sprintf("&PS=%s", $nPaymentStatus);
	$sParams .= sprintf("&TM=%s", $ClearAccept::TESTMODE);
	$sParams .= sprintf("&AM=%s", $nAmount);
	$sParams .= sprintf("&TX=%s", $hPaymentRequest->{'paymentRequestId'});
	$sParams .= sprintf("&PR=%s", $sCaptureId);
	$sParams .= sprintf("&CD=%s", $hTransaction->{'transactionId'});
	$sParams .= sprintf("&DT=%s", ACTINIC::EncodeText2($sTimeStamp, $::FALSE));	# time stamp needs to be encoded
	$sParams .= sprintf("&PA=%s", $bPreAuthorise);
	$sParams .= sprintf("&CF=%s", $hTransaction->{'retrievalReferenceNumber'});
	#
	# Include payment category if not blank and not ClearAccept
	# Other values may be ApplePay or another wallet provider
	#
	if ($hTransaction->{'paymentMethodProvider'} ne 'ClearAccept')
		{
		$sParams .= sprintf("&PC=%s", $hTransaction->{'paymentMethodProvider'});
		}
	#
	# Include card number and expiryif not ApplePay
	#
	if ($hTransaction->{'paymentMethodProvider'} ne 'ApplePay')
		{
		$sParams .= sprintf("&CS=%s", $hTransaction->{'cardScheme'});
		$sParams .= sprintf("&CM=%s", $hTransaction->{'cardNumber'});
		$sParams .= sprintf("&CE=%s", $hTransaction->{'expirationDate'});
		}
	#
	# Include 3DS results if present
	#
	if (defined $hTransaction->{'threeds'})
		{
		$sParams .= sprintf("&3E=%s", Translate3dsEnrolled($hTransaction->{'threeds'}{'enrolled'}));
		$sParams .= sprintf("&3R=%s", Translate3dsStatus($hTransaction->{'threeds'}{'threedsStatus'}));
		$sParams .= sprintf("&3L=%s", $hTransaction->{'threeds'}{'liabilityShiftStatus'});
		}
	#
	# CVV VERIFICATION
	#
	$sParams .= sprintf("&RC=%s", $hTransaction->{'cvvMatch'});
	#
	# AVS VERIFICATION
	# ClearAccept return one field for both Address and Post Code validation
	#
	# Live system expected values
	#	MATCH
	#	MISMATCH
	#	ERROR
	#	NOT_VERIFIED
	#	ADDRESS_MATCH_POSTAL_CODE_MISMATCH
	#	ADDRESS_MISMATCH_POSTAL_CODE_MATCH
	#	ADDRESS_MATCH_POSTAL_CODE_NOT_VERIFIED
	#	ADDRESS_NOT_VERIFIED_POSTAL_CODE_MATCH
	#
	if ('ADDRESS_MATCH_POSTAL_CODE_MISMATCH' eq $hTransaction->{'avsMatch'})
		{
		$sParams .= sprintf("&RA=%s", 'Matched');
		$sParams .= sprintf("&ZR=%s", 'Not Matched');
		}
	elsif ('ADDRESS_MISMATCH_POSTAL_CODE_MATCH' eq $hTransaction->{'avsMatch'})
		{
		$sParams .= sprintf("&RA=%s", 'Not Matched');
		$sParams .= sprintf("&ZR=%s", 'Matched');
		}
	elsif ('ADDRESS_MATCH_POSTAL_CODE_NOT_VERIFIED' eq $hTransaction->{'avsMatch'})
		{
		$sParams .= sprintf("&RA=%s", 'Matched');
		$sParams .= sprintf("&ZR=%s", 'Not Verified');
		}
	elsif ('ADDRESS_NOT_VERIFIED_POSTAL_CODE_MATCH' eq $hTransaction->{'avsMatch'})
		{
		$sParams .= sprintf("&RA=%s", 'Not Verified');
		$sParams .= sprintf("&ZR=%s", 'Matched');
		}
	else
		{
		$sParams .= sprintf("&RA=%s", $hTransaction->{'avsMatch'});
		$sParams .= sprintf("&ZR=%s", $hTransaction->{'avsMatch'});
		}
	#
	# Pass back the token if it is a permanent token
	#
	my $sToken = $hPaymentRequest->{'token'};
	if ($sToken =~ /^cardperm_/i)
		{
		$sParams .= sprintf("&CH=%s", $sToken);
		}
	#
	# Create the signature, must append the & as SDD expects it this way
	#
	$sParams .= '&';
	my $sSignature = ACTINIC::GetMD5Hash($sParams);
	$sParams .= sprintf("SN=%s", $sSignature);
	#
	# Record the authorization and submits to MailChimp if required
	#
	LogData(sprintf("ClearAccept::RecordAuthorization: calling RecordAuthorization with %s", $sParams));
	my $sError = main::RecordAuthorization(\$sParams);
	$::g_InputHash{ACTION} = $sAction;				# restore the original action
	if (length $sError)
		{
		#
		# Record any error to error.err
		#
		RecordErrors(sprintf("ClearAccept::RecordAuthorization: RecordAuthorization returned  %s", $sError));
		}
	else
		{
		LogData(sprintf("ClearAccept::RecordAuthorization: finished successfully"));
		}

	return ($::SUCCESS, '');

	}

#######################################################
#
# Translate3dsEnrolled - translate 3DS Enrolled if known
#
# Params:	0 - value to translate
#
# Returns:	0 - translated value
#
#######################################################

sub Translate3dsEnrolled
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in Translate3dsEnrolled ($#_)", __LINE__, __FILE__);
	my $s3dsEnrolled = shift;
	if ($s3dsEnrolled =~ m/^Y$/i)
		{
		$s3dsEnrolled = 'Yes';
		}
	elsif ($s3dsEnrolled =~ m/^N$/i)
		{
		$s3dsEnrolled = 'No';
		}
	elsif ($s3dsEnrolled =~ m/^U$/i)
		{
		$s3dsEnrolled = 'Undetermined';
		}
	return $s3dsEnrolled;
	}

#######################################################
#
# Translate3dsStatus - translate 3DS Status if known
#
# Params:	0 - value to translate
#
# Returns:	0 - translated value
#
#######################################################

sub Translate3dsStatus
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in Translate3dsStatus ($#_)", __LINE__, __FILE__);
	my $s3dsStatus = shift;
	if ($s3dsStatus =~ m/^Y$/i)
		{
		$s3dsStatus = 'Success';
		}
	elsif ($s3dsStatus =~ m/^N$/i)
		{
		$s3dsStatus = 'Failed';
		}
	elsif ($s3dsStatus =~ m/^A$/i)
		{
		$s3dsStatus = 'Authenticated';
		}
	elsif ($s3dsStatus =~ m/^U$/i)
		{
		$s3dsStatus = 'Unable to Auth.';
		}
	elsif ($s3dsStatus =~ m/^R$/i)
		{
		$s3dsStatus = 'Auth. request reject';
		}
	return $s3dsStatus;
	}

#######################################################
#
# IsSameCurrency - Check if payment currency is as expected
#
#	Input:	0 - currency code
#
#	Returns:	0 - true if same as expected
#
#######################################################

sub IsSameCurrency
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in IsSameCurrency ($#_)", __LINE__, __FILE__);
	my $sCurrency = shift;
	if ($sCurrency ne GetOrderCurrencyCode())
		{
		#
		# Not fully paid if different currency
		#
		return ($::FALSE);
		}
	return($::TRUE);
	}

#######################################################
#
# GetOrderCurrencyCode - Get the order currency code
#
#	Returns:	0 - the currency code
#
#######################################################

sub GetOrderCurrencyCode
	{
#? ACTINIC::ASSERT($#_ == -1, "Invalid argument count in GetOrderCurrencyCode ($#_)", __LINE__, __FILE__);
	#
	# Get the order currency
	#
	return ($$::g_pCatalogBlob{SINTLSYMBOLS});
	}

#######################################################
#
# GetCardDetails - Constructs a card description
#
# Params:	0 - authorisation hash
#
# Returns:	0 - card description or undef
#
#######################################################

sub GetCardDetails
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in GetCardDetails ($#_)", __LINE__, __FILE__);
	my $pAuthorisation = shift;

	if (defined $pAuthorisation)
		{
		return(sprintf("%s *%s",
			$pAuthorisation->{'cardScheme'},
			$pAuthorisation->{'cardNumber'}));		# not the full number
		}

	return (undef);
	}

#######################################################
#
# ValidateShippingAndTax - Validate shipping and tax
#
#	Returns:	0 - status $::SUCCESS or $::FAILURE
#				1 - error message if status not $::SUCCESS
#
#######################################################

sub ValidateShippingAndTax
	{
	my $sError = main::ValidateShipCharge($::TRUE);
	if (length $sError < 1)
		{
		#
		# Only try to validate the tax if the shipping was okay
		#
		$sError = ActinicOrder::ValidateTax($::TRUE);
		}
	my $nStatus = (length $sError < 1) ? $::SUCCESS : $::FAILURE;
	return ($nStatus, $sError);
	}

#######################################################
#
# AlreadyPaid - Check if order is already paid for
#
#	Requires: 	Payment Request hash
#
#	Returns:	0 - $::TRUE if already paid for
#
#######################################################

sub AlreadyPaid
	{
	my $hPaymentRequest = shift;

	if ($::Session->IsPaymentMade())					# already paid, not necessarily with ClearAccept
		{
		if ('confirmed' eq $hPaymentRequest->{'status'})
			{
			if (($::g_PaymentInfo{'ORDERNUMBER'} eq $hPaymentRequest->{'merchantReferences'}{'transactionReference'}) &&
				 (ActinicOrder::GetOrderTotal($::TRUE) == $hPaymentRequest->{'amount'}))
				{
				LogData(sprintf("ClearAccept::AlreadyPaid: Paid with method %s", $::g_PaymentInfo{'METHOD'}));
				return $::TRUE;
				}
			else
				{
				LogData(sprintf("ClearAccept::AlreadyPaid: Paid with method %s but order has changed", $::g_PaymentInfo{'METHOD'}));
				}
			}
		}
	return $::FALSE;
	}

#######################################################
#
# ReturnReceiptUrl - Returns receipt request URL
#
#######################################################

sub ReturnReceiptUrl
	{
	#
	# This intentionally does not save the session file
	#
	LogData(sprintf("ClearAccept::Prevented duplicate payment for order %s, value %s",
			$::g_PaymentInfo{'ORDERNUMBER'},
			ActinicOrder::GetOrderTotal($::TRUE)));
	my %hJsonResponse;
	$hJsonResponse{'success_url'} = main::GetReceiptUrl($::g_PaymentInfo{'ORDERNUMBER'});
	my $sJsonResponse = ACTINIC::EncodeJson(\%hJsonResponse);
	ACTINIC::PrintJSON($sJsonResponse);
	}

#######################################################
#
# SaveSessionAndSendResponse - Save session file and
#											send the JSON response
#
#	Input: 	0 - JSON response as a hash
#
#######################################################

sub SaveSessionAndSendResponse
	{
	my $sResponse = shift;
	#
	# Ensure checkout data is saved
	#
	main::UpdateCheckoutRecord();
	#
	# Make sure the session is saved
	#
	$::Session->SaveSession();
	my $sJsonResponse = ACTINIC::EncodeJson($sResponse);
	LogData("ClearAccept::SaveSessionAndSendResponse: Response is $sJsonResponse");
	ACTINIC::PrintJSON($sJsonResponse);
	}

######################################################################
#
# GetCachedToken	- gets a cached ClearAccept token
#
# Input		0 -	API id 0=>TRANSACT, 1=>DATA
#
# Returns	0 -	token or undef if failed
#
######################################################################

sub GetCachedToken
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in GetCachedToken ($#_)", __LINE__, __FILE__);
	my $nAPI = shift;
#? ACTINIC::ASSERT($nAPI > -1 && $nAPI < 3, "Invalid API index in GetCachedToken ($nAPI)", __LINE__, __FILE__);
	#
	# Use the cached token if we already got one
	# We are assuming here that a single request will be quicker than $::OAUTH2_THRESHOLD
	#
	if ((defined @ClearAccept::TOKEN[$nAPI]) &&
		 (@ClearAccept::TOKEN[$nAPI] ne ''))
		{
		LogData("ClearAccept::GetCachedToken: Using cached token");
		return (@ClearAccept::TOKEN[$nAPI]);
		}
	#
	# Get the current token, if any, from the session file
	#
	@ClearAccept::TOKEN			= split(',', $::Session->GetProviderParam($::PAYMENT_CLEARACCEPT, $::CLEARACCEPT_ACCESS_TOKEN));
	@ClearAccept::EXPIRY_TIME	= split(',', $::Session->GetProviderParam($::PAYMENT_CLEARACCEPT, $::CLEARACCEPT_EXPIRY_TIME));

	if ((length(@ClearAccept::TOKEN[$nAPI]) > 0) &&
		 (time < (@ClearAccept::EXPIRY_TIME[$nAPI] - $::OAUTH2_THRESHOLD)))
		{
		#
		# Use the stored token if it is for the same resource and not about to expire
		#
		LogData("ClearAccept::GetCachedToken: Using stored token");
		return (@ClearAccept::TOKEN[$nAPI]);
		}
	#
	# Otherwise get a new token
	#
	LogData("ClearAccept::GetCachedToken: No current valid token so creating a new token");
	return (GetToken($nAPI));
	}

######################################################################
#
# GetToken	- gets a ClearAccept token
#
# Input		0 -	API id 0=>TRANSACT, 1=>DATA
#
# Returns	0 -	token or undef if failed
#
######################################################################

sub GetToken
	{
#? ACTINIC::ASSERT($#_ == 0, "Invalid argument count in GetToken ($#_)", __LINE__, __FILE__);
	my $nAPI = shift;
	#
	# Get a secure connection
	#
	my $sCredentials = Base64Encode(sprintf("%s:%s", @ClearAccept::PSP_CLIENT_ID[$nAPI], @ClearAccept::PSP_SECRET_KEY[$nAPI]));
	my $SSLConnection =  SSLConnection->new($ClearAccept::IDENTITYHOST, $ClearAccept::PORT, "/oauth2/token");
	
	$SSLConnection->SetRequestMethod('POST');
	$SSLConnection->SetRequestTimeout($ClearAccept::HTTP_TIMEOUT);
	$SSLConnection->SetHeaderValue('Authorization', 'Basic ' . $sCredentials);
	$SSLConnection->SetHeaderValue('Accept', 'application/json');
	#
	# Setting the response error level to NONE so this method
	# must log any unexpected responses status codes
	#
	$SSLConnection->SetRequestErrorLevel($::HTTP_ERROR_LEVEL_NONE);
	#
	# We can re-issue this request without causing any problems
	# if the previous request completed but could not respond 
	#
	my $bRetry = $::TRUE;
	my $nRetries = $ClearAccept::HTTP_MAX_RETRIES;
	while ($bRetry)
		{
		$bRetry = $::FALSE;
		$SSLConnection->SendRequest('grant_type=client_credentials');
		if ($SSLConnection->GetResponseCode() > 499)
			{
			if ($nRetries)
				{
				$nRetries--;
				sleep $ClearAccept::HTTP_RETRY_INTERVAL;
				LogData("ClearAccept::GetToken: Token[$nAPI] request failed, retrying request");
				$bRetry = $::TRUE;
				}
			}
		}
	if (($SSLConnection->GetResponseCode() > 399) &&
		 ($SSLConnection->GetResponseCode() < 500))
		{
		#
		# The request was successful but the data is invalid
		# This can be a JSON error structure indicating the type of error
		#
		my $phResult = $SSLConnection->GetResponseJSON();
		if (defined $phResult->{'error'})
			{
			RecordErrors(sprintf("ClearAccept::GetToken: SendRequest returned (%s) %s",
					$phResult->{'error'},
					$phResult->{'error_description'}));
			return (undef);
			}
		}
	#
	# Due to the way the error handling was done connection status
	# also means not 200 OK so we need to check the response code first
	#
	if ($SSLConnection->GetConnectStatus() == $::FALSE)
		{
		RecordErrors(sprintf("ClearAccept::GetToken: SendRequest returned (%s) %s",
				$SSLConnection->GetResponseCode(),
				$SSLConnection->GetConnectErrorMessage()));
		return (undef);
		}

	my ($phResponse) = $SSLConnection->GetResponseJSON();
	#
	# If we don't have a session object (webhook) then we can't save the token
	#
	if (defined $::Session)
		{
		@ClearAccept::TOKEN[$nAPI] = $phResponse->{'access_token'};
		#
		# When does the token expire?
		# Would be more accurate to use the page header date as the basis of the calculation
		# but Date::Parse and DateTime are not core perl
		# we will remove the threshold time 
		#
		@ClearAccept::EXPIRY_TIME[$nAPI] = time + $phResponse->{'expires_in'} - $::OAUTH2_THRESHOLD;	# expire  time
		#
		# We are going to store the token in a comma separated list.
		# The comma is safe to use as we are generating a token of type bearer and so RFC 6750 applies
		# which restricts the returned characters of the token to any letter or digit plus ._~+/
		#
		$::Session->SetProviderParam($::PAYMENT_CLEARACCEPT, $::CLEARACCEPT_ACCESS_TOKEN, join(',', @ClearAccept::TOKEN));
		$::Session->SetProviderParam($::PAYMENT_CLEARACCEPT, $::CLEARACCEPT_EXPIRY_TIME, join(',', @ClearAccept::EXPIRY_TIME));
		}
	return ($phResponse->{'access_token'});
	}

######################################################################
#
# SendApiRequest	- Send a request to the ClearAccept API server
#
# Input		0 -	API id 0=>TRANSACT, 1=>DATA
#				1 -	REST request
#				2 -	Mode (GET/POST etc)
#				3 -	pointer to params - may be hash, array or scalar
#				4 -	allow retry for 5xx errors (default $::TRUE)
#
# Returns	0 -	status $::SUCCESS or $::FAILURE
#				1 -	error message if not $::SUCCESS
#				2 -	response hash
#				3 -	server response code
#
######################################################################

sub SendApiRequest
	{
	my $nAPI = shift;
	my $sRequest = shift;
	my $sMode = shift;
	my $pParams = shift;
	my $bRetryAllowed = shift;
	if (!defined $bRetryAllowed)
		{
		$bRetryAllowed = $::TRUE;
		}

	my ($nStatus, $sError, $hResponse, $nResponseStatus);

	my $nRetries = $ClearAccept::HTTP_MAX_RETRIES;

	while ($nRetries > 0)
		{
		($nStatus, $sError, $hResponse, $nResponseStatus) = SendRequest($nAPI, $ClearAccept::PROCESS_SCRIPT_HOST, $ClearAccept::PORT, "/v$ClearAccept::PSP_API_VERSION/$sRequest",
				$sMode, $pParams);
		my $b5xxError = (defined $nResponseStatus && $nResponseStatus > 499) ? $::TRUE : $::FALSE;
		if (($nStatus == $::SUCCESS) ||
			 !$b5xxError)
			{
			#
			# Return if success or not a 5xx error
			#
			return ($nStatus, $sError, $hResponse, $nResponseStatus);
			}
		#
		# We have a 5xx error
		#
		if (!$bRetryAllowed)
			{
			#
			# We don't want to retry the request
			#
			LogData("ClearAccept::SendApiRequest: Encountered $nResponseStatus error, no retry");
			return ($nStatus, $sError, $hResponse, $nResponseStatus);
			}
		$nRetries--;
		if ($nRetries > 0)
			{
			sleep $ClearAccept::HTTP_RETRY_INTERVAL;	# wait before retry
			LogData("ClearAccept::SendApiRequest: Encountered $nResponseStatus error, retrying");
			}
		}
	#
	# Return whatever was the last response
	#
	return ($nStatus, $sError, $hResponse, $nResponseStatus);
	}

######################################################################
#
# SendRequest	- Send a request to the ClearAccept server
#
# Input		0 -	API id 0=>TRANSACT, 1=>DATA
#				1 -	server host
#				2 -	server port
#				3 -	REST request
#				4 -	Mode (GET/POST etc)
#				5 -	pointer to params - may be hash, array or scalar
#
# Returns	0 -	status $::SUCCESS or $::FAILURE
#				1 -	error message if not $::SUCCESS
#				2 -	response hash or response status if error
#
######################################################################

sub SendRequest
	{
	my $nAPI = shift;
	my $sHost = shift;
	my $nPort = shift;
	my $sRequest = shift;
	my $sMode = shift;
	my $pParams = shift;
	#
	# Get the cached access token
	#
	my $sToken = GetCachedToken($nAPI);
	if (!defined $sToken)
		{
		#
		# Could not get the access token
		#
		RecordErrors('ClearAccept::SendRequest: Could not get the access token');
		return ($::FAILURE, $ClearAccept::ERRORMSG);
		}
	#
	# Get a secure connection
	#
	my $SSLConnection =  SSLConnection->new($sHost, $nPort, $sRequest);
	
	$SSLConnection->SetRequestMethod($sMode);
	$SSLConnection->SetHeaderValue('Content-Type', 'application/json');
	$SSLConnection->SetHeaderValue("Authorization", "Bearer " . $sToken);
	if (length $ClearAccept::PSP_PLATFORM_ID)
		{
		$SSLConnection->SetHeaderValue("platformId", $ClearAccept::PSP_PLATFORM_ID);
		}
	else
		{
		RecordErrors("Platform ID is not set");
		}
	if (length $ClearAccept::PSP_MERCHANT_ID)
		{
		$SSLConnection->SetHeaderValue("merchantId", $ClearAccept::PSP_MERCHANT_ID);
		}
	else
		{
		RecordErrors("Merchant ID is not set");
		}
	$SSLConnection->SetRequestTimeout($ClearAccept::HTTP_TIMEOUT);
	#
	# Setting the response error level to NONE so this method
	# must log any unexpected responses status codes
	#
	$SSLConnection->SetRequestErrorLevel($::HTTP_ERROR_LEVEL_NONE);
	#
	# Get the parameters as a JSON string if defined
	# Params may be a HASH, ARRAY or string
	#
	my $sContent = "";
	if ((ref($pParams) eq 'HASH') ||
		 (ref($pParams) eq 'ARRAY'))
		{
		$sContent = (defined $pParams) ? ACTINIC::EncodeJson($pParams) : '';
		}
	elsif ($pParams ne '')
		{
		$sContent = $pParams;
		}
	my $bFetchedToken = $::FALSE;
	#
	# The request will re-use the last acquired token but if the token has
	# expired then a new token will be acquired and the request re-tried
	#
	my $bRetry = $::TRUE;
	my $nRetries = 0;										# retry not supported as no idempotency

	while ($bRetry)
		{
		$bRetry = $::FALSE;
		$SSLConnection->SendRequest($sContent);
		if ($SSLConnection->GetResponseCode() == 429)	# rate exceeded
			{
			if ($nRetries)
				{
				$nRetries--;
				sleep $ClearAccept::HTTP_RETRY_INTERVAL;
				LogData("ClearAccept::SendRequest: Rate exceeded, retrying $sMode $sRequest request");
				$bRetry = $::TRUE;
				next;
				}
			}
		#
		# Due to the way the error handling was done connection status
		# also means not 200 OK so we need to check the response code first
		#
		if ($SSLConnection->GetResponseCode() == 401)
			{
			#
			# No valid API key provided. GetToken() should be called
			# but we shall only do this once
			#
			if (!$bFetchedToken)
				{
				$SSLConnection->SetHeaderValue("Authorization", "Bearer " . GetToken($nAPI));
				$bFetchedToken = $::TRUE;
				$bRetry = $::TRUE;						# retry irrespective of max retries
				next;
				}
			RecordErrors('ClearAccept::SendRequest: ' . $SSLConnection->GetConnectErrorMessage());
			return ($::FAILURE, $ClearAccept::ERRORMSG);
			}
		}

	my $sJsonResponse = $SSLConnection->GetResponseJSON();
	if (($SSLConnection->GetResponseCode() == 400) ||
		 ($SSLConnection->GetResponseCode() == 422))
		{
		print "Set-Cookie: CADATAERROR=1; PATH=/; SameSite=Lax\r\n";
		#
		# We have some processing or business error
		# Construct a message for the buyer
		#
		my $sMessage;
		my $paDetails = $sJsonResponse->{'errors'};
		foreach my $hDetail (@{$paDetails})
			{
			LogData('ClearAccept::SendRequest: ' . sprintf("%s (%s)", $hDetail->{'message'}, $hDetail->{'field'}));
			return ($::FAILURE, $hDetail->{'message'});
			}
		}

	if ($SSLConnection->GetResponseCode() > 299)
		{
		#
		# All 2xx codes are okay but not 3xx, 4xx or 5xx
		#
		RecordErrors(sprintf("ClearAccept::SendRequest: %s (%s)",
				$SSLConnection->GetConnectErrorMessage(),
				$SSLConnection->GetResponseCode()));
		return ($::FAILURE, $ClearAccept::ERRORMSG, undef, $SSLConnection->GetResponseCode());
		}
	return ($::SUCCESS, "", $sJsonResponse);
	}

#######################################################
#
# Base64Encode - Encode a string with Base 64 encoding
#
#	Input: 	0 - string to encode
#
#	Returns:	1 - Base 64 encoded string
#
#######################################################

sub Base64Encode ($;$)
	{
	my $res = "";
	my $eol = $_[1];
	$eol = "\n" unless defined $eol;
	pos($_[0]) = 0;                       		   # ensure start at the beginning
	
	$res = join '', map( pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
	
	$res =~ tr|` -_|AA-Za-z0-9+/|;               # `# help emacs
	# fix padding at the end
	my $padding = (3 - length($_[0]) % 3) % 3;
	$res =~ s/.{$padding}$/'=' x $padding/e if $padding;
	return $res;
	}

#######################################################
#
# RecordErrors - Wrapper for the RecordErrors in ACTINIC.pm
#				Pre-pends web hook message id if present
#
#	Input: 	0 - message to log
#
#######################################################

sub RecordErrors
	{
	my $sMessage = shift;
	ACTINIC::RecordErrors($sMessage);
	}

#######################################################
#
# LogData - Wrapper for the LogData in OrderScript.pl
#				Pre-pends web hook message id if present
#
#	Input: 	0 - message to log
#
#######################################################

sub LogData
	{
	my $sMessage = shift;
	ACTINIC::LogData($sMessage, $::DC_ORDERSCRIPT);
	}

#######################################################
#
# END OF ClearAccept PACKAGE
#
#######################################################
#
# Must be at the end as any following code will not be parsed
#
return ($::SUCCESS);
#
# End of File
