diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index 31f02334..e34811e1 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -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. */ @@ -25,7 +53,7 @@ abstract class AbstractRequest extends OmnipayAbstractRequest implements Constan /** * @var string The protocol version number. */ - protected $VPSProtocol = '3.00'; + protected $VPSProtocol = '4.00'; /** * @var string Endpoint base URLs. diff --git a/src/Message/DirectAuthorizeRequest.php b/src/Message/DirectAuthorizeRequest.php index 19325f24..80cd04bf 100644 --- a/src/Message/DirectAuthorizeRequest.php +++ b/src/Message/DirectAuthorizeRequest.php @@ -56,6 +56,30 @@ 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'] ?? null; + $data['BrowserUserAgent'] = $_SERVER['HTTP_USER_AGENT'] ?? null; + $data['ChallengeWindowSize'] = $this->getChallengeWindowSize() ?: static::CHALLENGE_WINDOW_SIZE_05; + + // Proctocol v4.00 - if BrowserJavascriptEnabled + $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; diff --git a/src/Message/DirectCompleteAuthorizeRequest.php b/src/Message/DirectCompleteAuthorizeRequest.php index 973ae88e..9ae6b10e 100644 --- a/src/Message/DirectCompleteAuthorizeRequest.php +++ b/src/Message/DirectCompleteAuthorizeRequest.php @@ -14,12 +14,32 @@ public function getService() return static::SERVICE_DIRECT3D; } + /** + * @return array|mixed|string[] + * @throws InvalidResponseException + */ 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. + // New 3D secure logic + if ($this->httpRequest->request->has('cres')) { + $CRes = $this->httpRequest->request->get('cres'); + $VPSTxId = $this->httpRequest->request->get('threeDSSessionData'); + + if (!$VPSTxId) { + throw new InvalidResponseException('3DSecure: Missing VPSTxId'); + } + + if (!$CRes) { + throw new InvalidResponseException('3DSecure: Missing CRes'); + } + + return compact('CRes', 'VPSTxId'); + } + $data = array( 'MD' => $this->getMd() ?: $this->httpRequest->request->get('MD'), 'PARes' => $this->getPaRes() ?: $this->httpRequest->request->get('PaRes'), diff --git a/src/Message/Response.php b/src/Message/Response.php index 34d54d34..1a6653d9 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -96,6 +96,13 @@ public function getRedirectMethod() public function getRedirectData() { if ($this->isRedirect()) { + if ($creq = $this->getDataItem('CReq')) { + return [ + 'creq' => $creq, + 'threeDSSessionData' => $this->getVPSTxId(), + ]; + } + return array( 'PaReq' => $this->getDataItem('PAReq'), 'TermUrl' => $this->getRequest()->getReturnUrl(), diff --git a/src/Message/SharedRepeatAuthorizeRequest.php b/src/Message/SharedRepeatAuthorizeRequest.php index a902c25d..46d49ffb 100644 --- a/src/Message/SharedRepeatAuthorizeRequest.php +++ b/src/Message/SharedRepeatAuthorizeRequest.php @@ -90,6 +90,16 @@ public function getData() $data['BasketXML'] = $basketXML; } + // Protocol v4.00 support + $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; } diff --git a/src/Traits/GatewayParamsTrait.php b/src/Traits/GatewayParamsTrait.php index 83ccdd26..760f0bce 100644 --- a/src/Traits/GatewayParamsTrait.php +++ b/src/Traits/GatewayParamsTrait.php @@ -5,6 +5,8 @@ //use Omnipay\Common\Exception\InvalidResponseException; //use Omnipay\Common\Message\NotificationInterface; //use Omnipay\SagePay\Message\Response; +use Omnipay\SagePay\AbstractGateway; +use Omnipay\SagePay\Message\AbstractRequest; /** * Parameters that can be set at the gateway class, and so @@ -257,4 +259,293 @@ public function setDisableUtf8Decode($value) { return $this->setParameter('disableUtf8Decode', $value); } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setThreeDSNotificationURL($value) + { + return $this->setParameter('ThreeDSNotificationURL', $value); + } + + /** + * @return mixed + */ + public function getThreeDSNotificationURL() + { + return $this->getParameter('ThreeDSNotificationURL'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setBrowserJavascriptEnabled($value) + { + return $this->setParameter('BrowserJavascriptEnabled', $value); + } + + /** + * @return mixed + */ + public function getBrowserJavascriptEnabled() + { + return $this->getParameter('BrowserJavascriptEnabled'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setBrowserLanguage($value) + { + return $this->setParameter('BrowserLanguage', $value); + } + + /** + * @return mixed + */ + public function getBrowserLanguage() + { + return $this->getParameter('BrowserLanguage'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setChallengeWindowSize($value) + { + return $this->setParameter('ChallengeWindowSize', $value); + } + + /** + * @return mixed + */ + public function getChallengeWindowSize() + { + return $this->getParameter('ChallengeWindowSize'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setBrowserJavaEnabled($value) + { + return $this->setParameter('BrowserJavaEnabled', $value); + } + + /** + * @return mixed + */ + public function getBrowserJavaEnabled() + { + return $this->getParameter('BrowserJavaEnabled'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setBrowserColorDepth($value) + { + return $this->setParameter('BrowserColorDepth', $value); + } + + /** + * @return mixed + */ + public function getBrowserColorDepth() + { + return $this->getParameter('BrowserColorDepth'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setBrowserScreenHeight($value) + { + return $this->setParameter('BrowserScreenHeight', $value); + } + + /** + * @return mixed + */ + public function getBrowserScreenHeight() + { + return $this->getParameter('BrowserScreenHeight'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setBrowserScreenWidth($value) + { + return $this->setParameter('BrowserScreenWidth', $value); + } + + /** + * @return mixed + */ + public function getBrowserScreenWidth() + { + return $this->getParameter('BrowserScreenWidth'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setBrowserTZ($value) + { + return $this->setParameter('BrowserTZ', $value); + } + + /** + * @return mixed + */ + public function getBrowserTZ() + { + return $this->getParameter('BrowserTZ'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setInitiatedType($value) + { + return $this->setParameter('InitiatedType', $value); + } + + /** + * @return mixed + */ + public function getInitiatedType() + { + return $this->getParameter('InitiatedType'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setCOFUsage($value) + { + return $this->setParameter('COFUsage', $value); + } + + /** + * @return mixed + */ + public function getCOFUsage() + { + return $this->getParameter('COFUsage'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setMITType($value) + { + return $this->setParameter('MITType', $value); + } + + /** + * @return mixed + */ + public function getMITType() + { + return $this->getParameter('MITType'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setSchemeTraceID($value) + { + return $this->setParameter('SchemeTraceID', $value); + } + + /** + * @return mixed + */ + public function getSchemeTraceID() + { + return $this->getParameter('SchemeTraceID'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setRecurringExpiry($value) + { + return $this->setParameter('RecurringExpiry', $value); + } + + /** + * @return mixed + */ + public function getRecurringExpiry() + { + return $this->getParameter('RecurringExpiry'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setRecurringFrequency($value) + { + return $this->setParameter('RecurringFrequency', $value); + } + + /** + * @return mixed + */ + public function getRecurringFrequency() + { + return $this->getParameter('RecurringFrequency'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setACSTransID($value) + { + return $this->setParameter('ACSTransID', $value); + } + + /** + * @return mixed + */ + public function getACSTransID() + { + return $this->getParameter('ACSTransID'); + } + + /** + * @param $value + * @return AbstractGateway|AbstractRequest + */ + public function setDSTransID($value) + { + return $this->setParameter('DSTransID', $value); + } + + /** + * @return mixed + */ + public function getDSTransID() + { + return $this->getParameter('DSTransID'); + } } diff --git a/src/Traits/ResponseFieldsTrait.php b/src/Traits/ResponseFieldsTrait.php index a14525aa..e7b5c3b0 100644 --- a/src/Traits/ResponseFieldsTrait.php +++ b/src/Traits/ResponseFieldsTrait.php @@ -213,6 +213,30 @@ public function getSurcharge() return $this->getDataItem('Surcharge'); } + /** + * @return string + */ + public function getDSTransID() + { + return $this->getDataItem('DSTransID'); + } + + /** + * @return string + */ + public function getACSTransID() + { + return $this->getDataItem('ACSTransID'); + } + + /** + * @return string + */ + public function getSchemeTraceID() + { + return $this->getDataItem('SchemeTraceID'); + } + /** * Raw expiry date for the card, "MMYY" format by default. * The expiry date is available for Sage Pay Direct responses, even if the diff --git a/src/Traits/ServerNotifyTrait.php b/src/Traits/ServerNotifyTrait.php index 0d54111b..fd609fb6 100644 --- a/src/Traits/ServerNotifyTrait.php +++ b/src/Traits/ServerNotifyTrait.php @@ -89,6 +89,10 @@ public function buildSignature() $this->getExpiryDate(), $this->getFraudResponse(), $this->getBankAuthCode(), + // New for protocol v4.00 + $this->getACSTransID(), + $this->getDSTransID(), + $this->getSchemeTraceID(), ) ); } diff --git a/tests/FormGatewayTest.php b/tests/FormGatewayTest.php index 4f3ad1a2..14d24e5c 100644 --- a/tests/FormGatewayTest.php +++ b/tests/FormGatewayTest.php @@ -52,7 +52,7 @@ public function testAuthorizeSuccess() $this->assertSame( [ - 'VPSProtocol' => '3.00', + 'VPSProtocol' => '4.00', 'TxType' => 'DEFERRED', 'Vendor' => 'example', 'Crypt' => '@BE1508740B97BEC235E9E8474168E3D4DEAD71C1395CDAC1A24F4784A71135AE797F5451F48EB2EAF5669EB3F6953F466FC3A03E5815E607C4DD18C03D7BDD61D176DB4DF131DC0F02DFC5145FFBA841651641BDAA405E72F4EF8849C2B2FD1F08763E3E66EAF3C479429A92014C7B8316F3B446BA09D28A821EE81C243E3DDD6F4C1F41D6ADEAC74D42B221645ADCC69E2F22ECDCAA010F63CCD02EAF0CE20F98439DAC7C31E528A7574656191150C1CC6CCD9FBEFCBFC9B34029FAB8F62DBC1C1618628FF0529B4E66B8AB857A79CC2FD0F14299A3505D22F964322755E6190EDD5BD42066D88154F950585236B6A2951D28BDA474E3FB17638DAA2F6304EAEA3AB9513DD0604D447000208D55DA4FDF544BEE00B5744170C1FC1E6DC6AADA07BEF6EB1FA46C14B99C3371491816A5C2CF7E03EDF6D58142767F7550DFFFE634FA56532605768FFEC1B20BEC32177816DDAB804149E9A301E495F11568F58896E90B1AC5776C2F12CE578955292F640A1E81213AAAA7856CD622FC241C65AC08215B94F933FD47050E0CFB2BFA85D7570A9D401B2366001AA377C50825B6D3893BDEC46D87F9121EC39DBF948F69399B8E842635556C7BC08E2E0C85CFA3EA2F9E4582ABDB3EB9668C83EDC34A5862F3CBC1A10935A189493D42D5C3FE4AF2BFF29B8464934B221B5A56B407F4E638B6766E6C706996A0252F2DDB30AFC8C1DA65F89987F4A9AA317BB104ED42F749DE3A43C39C515B67B3EA619D4092843DA551EEFC22CF672F53F753DEB93F1E8487BFE89C0100AFB9799238B24E523B0D6D53F824DEC0F6897F97FA507648D13276841B3B2E98030CBCD8E04F5E2C06C57BCC6F6C346440E620718F8877FFE969', @@ -92,7 +92,7 @@ public function testPurchaseSuccess() $this->assertSame( [ - 'VPSProtocol' => '3.00', + 'VPSProtocol' => '4.00', 'TxType' => 'PAYMENT', 'Vendor' => 'example', 'Crypt' => '@BE1508740B97BEC235E9E8474168E3D4DEAD71C1395CDAC1A24F4784A71135AE797F5451F48EB2EAF5669EB3F6953F466FC3A03E5815E607C4DD18C03D7BDD61D176DB4DF131DC0F02DFC5145FFBA841651641BDAA405E72F4EF8849C2B2FD1F08763E3E66EAF3C479429A92014C7B8316F3B446BA09D28A821EE81C243E3DDD6F4C1F41D6ADEAC74D42B221645ADCC69E2F22ECDCAA010F63CCD02EAF0CE20F98439DAC7C31E528A7574656191150C1CC6CCD9FBEFCBFC9B34029FAB8F62DBC1C1618628FF0529B4E66B8AB857A79CC2FD0F14299A3505D22F964322755E6190EDD5BD42066D88154F950585236B6A2951D28BDA474E3FB17638DAA2F6304EAEA3AB9513DD0604D447000208D55DA4FDF544BEE00B5744170C1FC1E6DC6AADA07BEF6EB1FA46C14B99C3371491816A5C2CF7E03EDF6D58142767F7550DFFFE634FA56532605768FFEC1B20BEC32177816DDAB804149E9A301E495F11568F58896E90B1AC5776C2F12CE578955292F640A1E81213AAAA7856CD622FC241C65AC08215B94F933FD47050E0CFB2BFA85D7570A9D401B2366001AA377C50825B6D3893BDEC46D87F9121EC39DBF948F69399B8E842635556C7BC08E2E0C85CFA3EA2F9E4582ABDB3EB9668C83EDC34A5862F3CBC1A10935A189493D42D5C3FE4AF2BFF29B8464934B221B5A56B407F4E638B6766E6C706996A0252F2DDB30AFC8C1DA65F89987F4A9AA317BB104ED42F749DE3A43C39C515B67B3EA619D4092843DA551EEFC22CF672F53F753DEB93F1E8487BFE89C0100AFB9799238B24E523B0D6D53F824DEC0F6897F97FA507648D13276841B3B2E98030CBCD8E04F5E2C06C57BCC6F6C346440E620718F8877FFE969', diff --git a/tests/Message/DirectTokenRequestTest.php b/tests/Message/DirectTokenRequestTest.php index 9373615b..fe0c1bf6 100644 --- a/tests/Message/DirectTokenRequestTest.php +++ b/tests/Message/DirectTokenRequestTest.php @@ -36,7 +36,7 @@ public function testGetData() { $data = $this->request->getData(); - $this->assertSame('3.00', $data['VPSProtocol']); + $this->assertSame('4.00', $data['VPSProtocol']); $this->assertSame('GBP', $data['Currency']); $this->assertSame('123', $data['VendorTxCode']); $this->assertSame('TOKEN', $data['TxType']); diff --git a/tests/Message/ServerTokenRegistrationRequestTest.php b/tests/Message/ServerTokenRegistrationRequestTest.php index 4e6b51e3..4fbc7b2b 100644 --- a/tests/Message/ServerTokenRegistrationRequestTest.php +++ b/tests/Message/ServerTokenRegistrationRequestTest.php @@ -33,7 +33,7 @@ public function testRequestDataIsCorrect() // Key assertion is that we are passing this TxType parameter in the request $this->assertSame('TOKEN', $data['TxType']); - $this->assertSame('3.00', $data['VPSProtocol']); + $this->assertSame('4.00', $data['VPSProtocol']); $this->assertSame('the_vendor', $data['Vendor']); $this->assertSame('123', $data['VendorTxCode']); $this->assertSame('http://notifyme.com', $data['NotificationURL']); diff --git a/tests/Message/SharedTokenRemovalRequestTest.php b/tests/Message/SharedTokenRemovalRequestTest.php index 62136f39..8a6fd2e7 100644 --- a/tests/Message/SharedTokenRemovalRequestTest.php +++ b/tests/Message/SharedTokenRemovalRequestTest.php @@ -26,7 +26,7 @@ public function testGetData() $this->assertSame('{ABCDE-ABCD-ABCD-ABCD-ABCDE}', $data['Token']); $this->assertSame('REMOVETOKEN', $data['TxType']); - $this->assertSame('3.00', $data['VPSProtocol']); + $this->assertSame('4.00', $data['VPSProtocol']); $this->assertSame('testvendor', $data['Vendor']); $this->assertArrayNotHasKey('AccountType', $data);