Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SCA / 3DSv2 compatibility for Direct and Server #163

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"sage pay",
"sagepay"
],

"homepage": "https://github.com/thephpleague/omnipay-sagepay",
"license": "MIT",
"authors": [
Expand All @@ -34,7 +35,7 @@
}
},
"require": {
"php": "^5.6|^7",
"php": "^5.6|^7|^8",
"omnipay/common": "~3.0"
},
"require-dev": {
Expand Down
41 changes: 40 additions & 1 deletion src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@ abstract class AbstractRequest extends OmnipayAbstractRequest implements Constan
{
use GatewayParamsTrait;

/**
* Flag whether customer's browser can run javascript.
*/
const BROWSER_JAVASCRIPT_YES = 1;
const BROWSER_JAVASCRIPT_NO = 0;

/**
* Fallback browser language
*/
const BROWSER_LANGUAGE = 'en-GB';

/**
* Dimensions of the challenge window to be displayed to the cardholder.
*
* 01 = 250 x 400
* 02 = 390 x 400
* 03 = 500 x 600
* 04 = 600 x 400
* 05 = Full screen
*
* @var string
*/
const CHALLENGE_WINDOW_SIZE_01 = '01';
const CHALLENGE_WINDOW_SIZE_02 = '02';
const CHALLENGE_WINDOW_SIZE_03 = '03';
const CHALLENGE_WINDOW_SIZE_04 = '04';
const CHALLENGE_WINDOW_SIZE_05 = '05';

/**
* @var string The service name, used in the endpoint URL.
*/
Expand Down Expand Up @@ -70,6 +98,17 @@ public function getTxType()
throw new InvalidRequestException('Transaction type not defined.');
}


public function getProtocol()
{
return $this->getParameter('protocol');
}

public function setProtocol($value)
{
return $this->setParameter('protocol', $value);
}

/**
* Basic authorisation, transaction type and protocol version.
*
Expand All @@ -79,7 +118,7 @@ protected function getBaseData()
{
$data = array();

$data['VPSProtocol'] = $this->VPSProtocol;
$data['VPSProtocol'] = $this->getProtocol() ?: $this->VPSProtocol;
$data['TxType'] = $this->getTxType();
$data['Vendor'] = $this->getVendor();
$data['AccountType'] = $this->getAccountType() ?: static::ACCOUNT_TYPE_E;
Expand Down
26 changes: 26 additions & 0 deletions src/Message/DirectAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,32 @@ protected function getBaseAuthorizeData()
$data['VendorTxCode'] = $this->getTransactionId();
$data['ClientIPAddress'] = $this->getClientIp();

$data['BrowserJavascriptEnabled'] = $this->getBrowserJavascriptEnabled() ?: static::BROWSER_JAVASCRIPT_NO;
$data['BrowserLanguage'] = $this->getBrowserLanguage() ?: static::BROWSER_LANGUAGE;
$data['ThreeDSNotificationURL'] = $this->getThreeDSNotificationURL();
$data['BrowserAcceptHeader'] = $_SERVER['HTTP_ACCEPT'];
$data['BrowserUserAgent'] = $_SERVER['HTTP_USER_AGENT'];
$data['ChallengeWindowSize'] = $this->getChallengeWindowSize() ?: static::CHALLENGE_WINDOW_SIZE_05;
// ----
// ---- "4.00" - required if BrowserJavascriptEnabled == "1"
$data['BrowserJavaEnabled'] = $this->getBrowserJavaEnabled();
$data['BrowserColorDepth'] = $this->getBrowserColorDepth();
$data['BrowserScreenHeight'] = $this->getBrowserScreenHeight();
$data['BrowserScreenWidth'] = $this->getBrowserScreenWidth();
$data['BrowserTZ'] = $this->getBrowserTZ();
// ----

// repeat payments required fields
$data['MITType'] = $this->getMITType();
$data['COFUsage'] = $this->getCOFUsage();
$data['InitiatedType'] = $this->getInitiatedType();
$data['SchemeTraceID'] = $this->getSchemeTraceID();
$data['RecurringExpiry'] = $this->getRecurringExpiry();
$data['RecurringFrequency'] = $this->getRecurringFrequency();
$data['ACSTransID'] = $this->getACSTransID();
$data['DSTransID'] = $this->getDSTransID();


$data['ApplyAVSCV2'] = $this->getApplyAVSCV2() ?: static::APPLY_AVSCV2_DEFAULT;
$data['Apply3DSecure'] = $this->getApply3DSecure() ?: static::APPLY_3DSECURE_APPLY;

Expand Down
26 changes: 17 additions & 9 deletions src/Message/DirectCompleteAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,27 @@ public function getService()

public function getData()
{
// Inconsistent letter case is intentional.
// The issuing bank will return PaRes, but the merchant
// site must send this result as PARes to Sage Pay.
if($this->httpRequest->request->has('cres')){
$data = array(
'CRes' => $this->httpRequest->request->get('cres'), // inconsistent caps are intentional
'VPSTxId' => $this->httpRequest->request->get('threeDSSessionData'),
);

$data = array(
'MD' => $this->getMd() ?: $this->httpRequest->request->get('MD'),
'PARes' => $this->getPaRes() ?: $this->httpRequest->request->get('PaRes'),
);
if (empty($data['CRes']) || empty($data['VPSTxId'])) {
throw new InvalidResponseException;
}
}else{
$data = array(
'MD' => $this->httpRequest->request->get('MD'),
'PARes' => $this->httpRequest->request->get('PaRes'), // inconsistent caps are intentional
);

if (empty($data['MD']) || empty($data['PARes'])) {
throw new InvalidResponseException;
if (empty($data['MD']) || empty($data['PARes'])) {
throw new InvalidResponseException;
}
}


return $data;
}

Expand Down
20 changes: 15 additions & 5 deletions src/Message/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,21 @@ public function getRedirectMethod()
public function getRedirectData()
{
if ($this->isRedirect()) {
return array(
'PaReq' => $this->getDataItem('PAReq'),
'TermUrl' => $this->getRequest()->getReturnUrl(),
'MD' => $this->getDataItem('MD'),
);
if (isset($this->data['CReq'])) {
// 3DSv2
return [
'creq' => $this->data['CReq'],
'threeDSSessionData' => $this->data['VPSTxId'],
];
} else {
// fallback from 3DSv2 to 3DSv1
return [
'PaReq' => $this->data['PAReq'],
'TermUrl' => $this->getRequest()->getReturnUrl(),
'MD' => $this->data['MD'],
];
}

}
}

Expand Down
13 changes: 13 additions & 0 deletions src/Message/SharedRepeatAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ public function getData()
$data['BasketXML'] = $basketXML;
}

// add v4 request fields
if ($this->getProtocol() == '4.00'){

$data['MITType'] = $this->getMITType();
$data['COFUsage'] = $this->getCOFUsage();
$data['InitiatedType'] = $this->getInitiatedType();
$data['SchemeTraceID'] = $this->getSchemeTraceID();
$data['RecurringExpiry'] = $this->getRecurringExpiry();
$data['RecurringFrequency'] = $this->getRecurringFrequency();
$data['ACSTransID'] = $this->getACSTransID();
$data['DSTransID'] = $this->getDSTransID();
}

return $data;
}

Expand Down
171 changes: 171 additions & 0 deletions src/Traits/GatewayParamsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,175 @@ public function setDisableUtf8Decode($value)
{
return $this->setParameter('disableUtf8Decode', $value);
}

public function setThreeDSNotificationURL($value)
{
return $this->setParameter('ThreeDSNotificationURL', $value);
}

public function getThreeDSNotificationURL()
{
return $this->getParameter('ThreeDSNotificationURL');
}

public function setBrowserJavascriptEnabled($value)
{
return $this->setParameter('BrowserJavascriptEnabled', $value);
}

public function getBrowserJavascriptEnabled()
{
return $this->getParameter('BrowserJavascriptEnabled');
}

public function setBrowserLanguage($value)
{
return $this->setParameter('BrowserLanguage', $value);
}

public function getBrowserLanguage()
{
return $this->getParameter('BrowserLanguage');
}

public function setChallengeWindowSize($value)
{
return $this->setParameter('ChallengeWindowSize', $value);
}

public function getChallengeWindowSize()
{
return $this->getParameter('ChallengeWindowSize');
}

public function setBrowserJavaEnabled($value)
{
return $this->setParameter('BrowserJavaEnabled', $value);
}

public function getBrowserJavaEnabled()
{
return $this->getParameter('BrowserJavaEnabled');
}


public function setBrowserColorDepth($value)
{
return $this->setParameter('BrowserColorDepth', $value);
}

public function getBrowserColorDepth()
{
return $this->getParameter('BrowserColorDepth');
}

public function setBrowserScreenHeight($value)
{
return $this->setParameter('BrowserScreenHeight', $value);
}

public function getBrowserScreenHeight()
{
return $this->getParameter('BrowserScreenHeight');
}

public function setBrowserScreenWidth($value)
{
return $this->setParameter('BrowserScreenWidth', $value);
}

public function getBrowserScreenWidth()
{
return $this->getParameter('BrowserScreenWidth');
}

public function setBrowserTZ($value)
{
return $this->setParameter('BrowserTZ', $value);
}

public function getBrowserTZ()
{
return $this->getParameter('BrowserTZ');
}

public function setInitiatedType($value)
{
return $this->setParameter('InitiatedType', $value);
}

public function getInitiatedType()
{
return $this->getParameter('InitiatedType');
}

public function setCOFUsage($value)
{
return $this->setParameter('COFUsage', $value);
}

public function getCOFUsage()
{
return $this->getParameter('COFUsage');
}

public function setMITType($value)
{
return $this->setParameter('MITType', $value);
}

public function getMITType()
{
return $this->getParameter('MITType');
}

public function setSchemeTraceID($value)
{
return $this->setParameter('SchemeTraceID', $value);
}

public function getSchemeTraceID()
{
return $this->getParameter('SchemeTraceID');
}

public function setRecurringExpiry($value)
{
return $this->setParameter('RecurringExpiry', $value);
}

public function getRecurringExpiry()
{
return $this->getParameter('RecurringExpiry');
}

public function setRecurringFrequency($value)
{
return $this->setParameter('RecurringFrequency', $value);
}

public function getRecurringFrequency()
{
return $this->getParameter('RecurringFrequency');
}

public function setACSTransID($value)
{
return $this->setParameter('ACSTransID', $value);
}

public function getACSTransID()
{
return $this->getParameter('ACSTransID');
}

public function setDSTransID($value)
{
return $this->setParameter('DSTransID', $value);
}

public function getDSTransID()
{
return $this->getParameter('DSTransID');
}
}
12 changes: 12 additions & 0 deletions src/Traits/ServerNotifyTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ public function buildSignature()
$this->getBankAuthCode(),
)
);
// new in v4
if ( $this->getDataItem('VPSProtocol') =='4.00'){
$signatureData = array_merge(
$signatureData,
array(
$this->getDataItem('ACSTransID'),
$this->getDataItem('DSTransID'),
$this->getDataItem('SchemeTraceID'),
)
);
}

}

return md5(implode('', $signatureData));
Expand Down