From 4953d3d37aa6e0bf976da141dfacf1eee7e34279 Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Sun, 13 Mar 2016 00:28:06 +0530 Subject: [PATCH 1/6] XOL-2799 support for card present transactions --- composer.json | 2 +- src/Message/AIMAuthorizeRequest.php | 26 ++++++++++++-- tests/AIMGatewayIntegrationTest.php | 17 +++++++++ tests/Message/AIMAuthorizeRequestTest.php | 42 +++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 59a25f1a..5eede3a6 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "psr-4": { "Omnipay\\AuthorizeNet\\" : "src/" } }, "require": { - "omnipay/common": "~2.4.2" + "omnipay/common": "~2.4.3" }, "require-dev": { "omnipay/tests": "~2.0" diff --git a/src/Message/AIMAuthorizeRequest.php b/src/Message/AIMAuthorizeRequest.php index f6dbbb48..88f24039 100644 --- a/src/Message/AIMAuthorizeRequest.php +++ b/src/Message/AIMAuthorizeRequest.php @@ -19,6 +19,7 @@ public function getData() $this->addPayment($data); $this->addCustomerIP($data); $this->addBillingData($data); + $this->addRetail($data); $this->addTestModeSetting($data); $this->addExtraOptions($data); @@ -31,9 +32,19 @@ protected function addPayment(\SimpleXMLElement $data) /** @var CreditCard $card */ $card = $this->getCard(); $card->validate(); - $data->transactionRequest->payment->creditCard->cardNumber = $card->getNumber(); - $data->transactionRequest->payment->creditCard->expirationDate = $card->getExpiryDate('my'); - $data->transactionRequest->payment->creditCard->cardCode = $card->getCvv(); + if ($card->getTracks()) { + // Card present + if ($track1 = $card->getTrack1()) { + $data->transactionRequest->payment->trackData->track1 = $track1; + } elseif ($track2 = $card->getTrack2()) { + $data->transactionRequest->payment->trackData->track2 = $track2; + } + } else { + // Card not present + $data->transactionRequest->payment->creditCard->cardNumber = $card->getNumber(); + $data->transactionRequest->payment->creditCard->expirationDate = $card->getExpiryDate('my'); + $data->transactionRequest->payment->creditCard->cardCode = $card->getCvv(); + } } protected function addCustomerIP(\SimpleXMLElement $data) @@ -43,4 +54,13 @@ protected function addCustomerIP(\SimpleXMLElement $data) $data->transactionRequest->customerIP = $ip; } } + + protected function addRetail(\SimpleXMLElement $data) + { + if ($this->getCard()->getTracks()) { + // Retail element is required for card present transactions + $data->transactionRequest->retail->marketType = 2; + $data->transactionRequest->retail->deviceType = 1; + } + } } diff --git a/tests/AIMGatewayIntegrationTest.php b/tests/AIMGatewayIntegrationTest.php index 299161d8..ed2f71a4 100644 --- a/tests/AIMGatewayIntegrationTest.php +++ b/tests/AIMGatewayIntegrationTest.php @@ -93,4 +93,21 @@ public function testPurchaseRefundAutoVoid() $response = $request->send(); $this->assertTrue($response->isSuccessful(), 'Automatic void should succeed'); } + + public function testPurchaseCardPresent() + { + $amount = rand(10, 100) . '.' . rand(0, 99); + $card = array( + 'number' => '4242424242424242', + 'expiryMonth' => rand(1, 12), + 'expiryYear' => gmdate('Y') + rand(1, 5), + 'tracks' => '%B4242424242424242^SMITH/JOHN ^2511126100000000000000444000000?;4242424242424242=25111269999944401?' + ); + $request = $this->gateway->purchase(array( + 'amount' => $amount, + 'card' => $card + )); + $response = $request->send(); + $this->assertTrue($response->isSuccessful()); + } } diff --git a/tests/Message/AIMAuthorizeRequestTest.php b/tests/Message/AIMAuthorizeRequestTest.php index 63520b42..97590fe7 100644 --- a/tests/Message/AIMAuthorizeRequestTest.php +++ b/tests/Message/AIMAuthorizeRequestTest.php @@ -34,6 +34,8 @@ public function testGetData() $setting = $data->transactionRequest->transactionSettings->setting[0]; $this->assertEquals('testRequest', $setting->settingName); $this->assertEquals('false', $setting->settingValue); + $this->assertObjectNotHasAttribute('trackData', $data->transactionRequest->payment); + $this->assertObjectNotHasAttribute('retail', $data->transactionRequest); } public function testGetDataTestMode() @@ -52,4 +54,44 @@ public function testShouldReturnExtraOptionsToDisableDuplicateWindowPeriod() $data = $this->request->getData(); $this->assertEquals('x_duplicate_window=0', strip_tags($data->extraOptions)); } + + public function testGetDataCardPresentTrack1() + { + $card = $this->getValidCard(); + $card['tracks'] = '%B4242424242424242^SMITH/JOHN ^2511126100000000000000444000000?;4242424242424242=25111269999944401?'; + $this->request->initialize(array( + 'amount' => '12.12', + 'card' => $card + )); + + $data = $this->request->getData(); + + $this->assertEquals('12.12', $data->transactionRequest->amount); + $this->assertEquals( + '%B4242424242424242^SMITH/JOHN ^2511126100000000000000444000000?', + $data->transactionRequest->payment->trackData->track1); + $this->assertObjectNotHasAttribute('creditCard', $data->transactionRequest->payment); + $this->assertEquals('2', $data->transactionRequest->retail->marketType); + $this->assertEquals('1', $data->transactionRequest->retail->deviceType); + } + + public function testGetDataCardPresentTrack2() + { + $card = $this->getValidCard(); + $card['tracks'] = ';4242424242424242=25111269999944401?'; + $this->request->initialize(array( + 'amount' => '12.12', + 'card' => $card + )); + + $data = $this->request->getData(); + + $this->assertEquals('12.12', $data->transactionRequest->amount); + $this->assertEquals( + ';4242424242424242=25111269999944401?', + $data->transactionRequest->payment->trackData->track2); + $this->assertObjectNotHasAttribute('creditCard', $data->transactionRequest->payment); + $this->assertEquals('2', $data->transactionRequest->retail->marketType); + $this->assertEquals('1', $data->transactionRequest->retail->deviceType); + } } From 07749f72a78ea4ead96d949ac47db9d6aa70a325 Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Mon, 14 Mar 2016 13:00:18 +0530 Subject: [PATCH 2/6] XOL-2799 give preference to track data over customer profile --- src/Message/AIMAbstractRequest.php | 6 +++ src/Message/AIMAuthorizeRequest.php | 2 +- src/Message/CIMAuthorizeRequest.php | 45 ++++++++++++++--------- tests/Message/CIMAuthorizeRequestTest.php | 15 ++++++++ 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/Message/AIMAbstractRequest.php b/src/Message/AIMAbstractRequest.php index 438bfb5c..df975d46 100644 --- a/src/Message/AIMAbstractRequest.php +++ b/src/Message/AIMAbstractRequest.php @@ -255,4 +255,10 @@ protected function addExtraOptions(\SimpleXMLElement $data) } return $data; } + + protected function isCardPresent() + { + // If the credit card has track data, then consider this a "card present" scenario + return ($card = $this->getCard()) && $card->getTracks(); + } } diff --git a/src/Message/AIMAuthorizeRequest.php b/src/Message/AIMAuthorizeRequest.php index 88f24039..6ce896d5 100644 --- a/src/Message/AIMAuthorizeRequest.php +++ b/src/Message/AIMAuthorizeRequest.php @@ -57,7 +57,7 @@ protected function addCustomerIP(\SimpleXMLElement $data) protected function addRetail(\SimpleXMLElement $data) { - if ($this->getCard()->getTracks()) { + if ($this->isCardPresent()) { // Retail element is required for card present transactions $data->transactionRequest->retail->marketType = 2; $data->transactionRequest->retail->deviceType = 1; diff --git a/src/Message/CIMAuthorizeRequest.php b/src/Message/CIMAuthorizeRequest.php index 7729eb21..6125a12f 100644 --- a/src/Message/CIMAuthorizeRequest.php +++ b/src/Message/CIMAuthorizeRequest.php @@ -11,29 +11,40 @@ class CIMAuthorizeRequest extends AIMAuthorizeRequest { protected function addPayment(\SimpleXMLElement $data) { - $this->validate('cardReference'); - - /** @var mixed $req */ - $req = $data->transactionRequest; - /** @var CardReference $cardRef */ - $cardRef = $this->getCardReference(false); - $req->profile->customerProfileId = $cardRef->getCustomerProfileId(); - $req->profile->paymentProfile->paymentProfileId = $cardRef->getPaymentProfileId(); - if ($shippingProfileId = $cardRef->getShippingProfileId()) { - $req->profile->shippingProfileId = $shippingProfileId; - } + if ($this->isCardPresent()) { + // Prefer the track data if present over the payment profile (better rate) + return parent::addPayment($data); + + } else { + $this->validate('cardReference'); + + /** @var mixed $req */ + $req = $data->transactionRequest; + /** @var CardReference $cardRef */ + $cardRef = $this->getCardReference(false); + $req->profile->customerProfileId = $cardRef->getCustomerProfileId(); + $req->profile->paymentProfile->paymentProfileId = $cardRef->getPaymentProfileId(); + if ($shippingProfileId = $cardRef->getShippingProfileId()) { + $req->profile->shippingProfileId = $shippingProfileId; + } - $desc = $this->getDescription(); - if (!empty($desc)) { - $req->order->description = $desc; + $desc = $this->getDescription(); + if (!empty($desc)) { + $req->order->description = $desc; + } + + return $data; } - return $data; } protected function addBillingData(\SimpleXMLElement $data) { - // Do nothing since billing information is already part of the customer profile - return $data; + if ($this->isCardPresent()) { + return parent::addBillingData($data); + } else { + // Do nothing since billing information is already part of the customer profile + return $data; + } } } diff --git a/tests/Message/CIMAuthorizeRequestTest.php b/tests/Message/CIMAuthorizeRequestTest.php index 2d3f998f..59cc0100 100644 --- a/tests/Message/CIMAuthorizeRequestTest.php +++ b/tests/Message/CIMAuthorizeRequestTest.php @@ -38,4 +38,19 @@ public function testShouldReturnExtraOptionsToDisableDuplicateWindowPeriod() $data = $this->request->getData(); $this->assertEquals('x_duplicate_window=0', strip_tags($data->extraOptions)); } + + public function testShouldUseTrackDataIfCardPresent() + { + $card = $this->getValidCard(); + $card['tracks'] = '%B4242424242424242^SMITH/JOHN ^2511126100000000000000444000000?;4242424242424242=25111269999944401?'; + $this->request->initialize(array( + 'card' => $card, + 'amount' => 21.00 + )); + + $data = $this->request->getData(); + + $this->assertObjectNotHasAttribute('profile', $data->transactionRequest); + $this->assertObjectHasAttribute('trackData', $data->transactionRequest->payment); + } } From abc99a741d9ce3aad9b6e099214332fc5b2667a1 Mon Sep 17 00:00:00 2001 From: Rushi Vishavadia Date: Tue, 15 Mar 2016 16:28:14 +0530 Subject: [PATCH 3/6] Fix JSCS issue (120 char limit) --- src/Message/AIMAbstractRequest.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Message/AIMAbstractRequest.php b/src/Message/AIMAbstractRequest.php index 7e5fcaf9..9c37a6f2 100644 --- a/src/Message/AIMAbstractRequest.php +++ b/src/Message/AIMAbstractRequest.php @@ -240,14 +240,15 @@ protected function addTransactionSettings(\SimpleXMLElement $data) $i = 0; // The test mode setting indicates whether or not this is a live request or a test request - $data->transactionRequest->transactionSettings->setting[$i]->settingName = 'testRequest'; - $data->transactionRequest->transactionSettings->setting[$i]->settingValue = $this->getTestMode() ? 'true' : 'false'; + $transactionRequest = $data->transactionRequest; + $transactionRequest->transactionSettings->setting[$i]->settingName = 'testRequest'; + $transactionRequest->transactionSettings->setting[$i]->settingValue = $this->getTestMode() ? 'true' : 'false'; // The duplicate window setting specifies the threshold for AuthorizeNet's duplicate transaction detection logic if (!is_null($this->getDuplicateWindow())) { $i++; - $data->transactionRequest->transactionSettings->setting[$i]->settingName = 'duplicateWindow'; - $data->transactionRequest->transactionSettings->setting[$i]->settingValue = $this->getDuplicateWindow(); + $transactionRequest->transactionSettings->setting[$i]->settingName = 'duplicateWindow'; + $transactionRequest->transactionSettings->setting[$i]->settingValue = $this->getDuplicateWindow(); } return $data; From ea42c0a589815f69e3cf54ad7f3b4ad90b838604 Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Mon, 21 Mar 2016 15:45:04 +0530 Subject: [PATCH 4/6] Point to origin omnipay-common --- composer.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 5eede3a6..2cd0e1c0 100644 --- a/composer.json +++ b/composer.json @@ -24,17 +24,11 @@ "homepage": "https://github.com/thephpleague/omnipay-authorizenet/contributors" } ], - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/xola/omnipay-common" - } - ], "autoload": { "psr-4": { "Omnipay\\AuthorizeNet\\" : "src/" } }, "require": { - "omnipay/common": "~2.4.3" + "omnipay/common": "~2.5" }, "require-dev": { "omnipay/tests": "~2.0" From 6b561aaa45cfb641cacc022a2a729b23cccd257b Mon Sep 17 00:00:00 2001 From: Anush Ramani Date: Fri, 29 Apr 2016 14:42:42 +0530 Subject: [PATCH 5/6] Allow overriding the `deviceType` --- src/AIMGateway.php | 30 +++++++++++++++++++++++ src/Message/AIMAbstractRequest.php | 10 ++++++++ src/Message/AIMAuthorizeRequest.php | 2 +- src/Message/SIMAbstractRequest.php | 10 ++++++++ tests/Message/AIMAuthorizeRequestTest.php | 6 +++-- 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/AIMGateway.php b/src/AIMGateway.php index fabfd05f..53b6eea1 100644 --- a/src/AIMGateway.php +++ b/src/AIMGateway.php @@ -28,6 +28,7 @@ public function getDefaultParameters() 'developerMode' => false, 'liveEndpoint' => 'https://api.authorize.net/xml/v1/request.api', 'developerEndpoint' => 'https://apitest.authorize.net/xml/v1/request.api', + 'deviceType' => 1 // Device used to make the transaction. Required for card present. "1" = Unknown. ); } @@ -97,6 +98,35 @@ public function setDuplicateWindow($value) return $this->setParameter('duplicateWindow', $value); } + public function getDeviceType() + { + return $this->getParameter('deviceType'); + } + + /** + * Sets the type of device used to collect the credit card data. A device type is required for card present + * transactions. + * + * 1 = Unknown + * 2 = Unattended Terminal + * 3 = Self Service Terminal + * 4 = Electronic Cash Register + * 5 = Personal Computer-Based Terminal + * 6 = AirPay + * 7 = Wireless POS + * 8 = Website + * 9 = Dial Terminal + * 10 = Virtual Terminal + * + * @see http://developer.authorize.net/api/reference/#payment-transactions-charge-a-credit-card + * @param $value + * @return $this + */ + public function setDeviceType($value) + { + return $this->setParameter('deviceType', $value); + } + /** * @param array $parameters * @return AIMAuthorizeRequest diff --git a/src/Message/AIMAbstractRequest.php b/src/Message/AIMAbstractRequest.php index 129b8d73..84f2d2f0 100644 --- a/src/Message/AIMAbstractRequest.php +++ b/src/Message/AIMAbstractRequest.php @@ -100,6 +100,16 @@ public function getEndpoint() return $this->getDeveloperMode() ? $this->getDeveloperEndpoint() : $this->getLiveEndpoint(); } + public function getDeviceType() + { + return $this->getParameter('deviceType'); + } + + public function setDeviceType($value) + { + return $this->setParameter('deviceType', $value); + } + /** * @return TransactionReference */ diff --git a/src/Message/AIMAuthorizeRequest.php b/src/Message/AIMAuthorizeRequest.php index f56135a0..7df44c8d 100644 --- a/src/Message/AIMAuthorizeRequest.php +++ b/src/Message/AIMAuthorizeRequest.php @@ -59,7 +59,7 @@ protected function addRetail(\SimpleXMLElement $data) if ($this->isCardPresent()) { // Retail element is required for card present transactions $data->transactionRequest->retail->marketType = 2; - $data->transactionRequest->retail->deviceType = 1; + $data->transactionRequest->retail->deviceType = $this->getDeviceType(); } } } diff --git a/src/Message/SIMAbstractRequest.php b/src/Message/SIMAbstractRequest.php index 6fb01c68..b8f52e6a 100644 --- a/src/Message/SIMAbstractRequest.php +++ b/src/Message/SIMAbstractRequest.php @@ -84,6 +84,16 @@ public function getDeveloperEndpoint() return $this->getParameter('developerEndpoint'); } + public function getDeviceType() + { + return $this->getParameter('deviceType'); + } + + public function setDeviceType($value) + { + return $this->setParameter('deviceType', $value); + } + /** * Base data used only for the AIM API. */ diff --git a/tests/Message/AIMAuthorizeRequestTest.php b/tests/Message/AIMAuthorizeRequestTest.php index 7f457115..2bc0b80a 100644 --- a/tests/Message/AIMAuthorizeRequestTest.php +++ b/tests/Message/AIMAuthorizeRequestTest.php @@ -63,7 +63,8 @@ public function testGetDataCardPresentTrack1() $card['tracks'] = '%B4242424242424242^SMITH/JOHN ^2511126100000000000000444000000?;4242424242424242=25111269999944401?'; $this->request->initialize(array( 'amount' => '12.12', - 'card' => $card + 'card' => $card, + 'deviceType' => 1 )); $data = $this->request->getData(); @@ -83,7 +84,8 @@ public function testGetDataCardPresentTrack2() $card['tracks'] = ';4242424242424242=25111269999944401?'; $this->request->initialize(array( 'amount' => '12.12', - 'card' => $card + 'card' => $card, + 'deviceType' => 1 )); $data = $this->request->getData(); From 7e11d7733e6fffd84236d2f04488c81f103f7f21 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sun, 8 Oct 2017 13:48:59 +0100 Subject: [PATCH 6/6] For PR #36 - validate card number only if card not present. --- src/AIMGateway.php | 20 +++++++++++++++++--- src/Message/AIMAuthorizeRequest.php | 10 ++++++++-- tests/Message/AIMAuthorizeRequestTest.php | 7 +++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/AIMGateway.php b/src/AIMGateway.php index 5b11c7af..37f3c883 100644 --- a/src/AIMGateway.php +++ b/src/AIMGateway.php @@ -14,6 +14,20 @@ */ class AIMGateway extends AbstractGateway { + /** + * The device type collecting credit card data. + */ + const DEVICE_TYPE_UNKNOWN = 1; + const DEVICE_TYPE_UNATTENDED_TERMINAL = 2; + const DEVICE_TYPE_SELF_SERVICE_TERMINAL = 3; + const DEVICE_TYPE_ELECTRONIC_CASH_REGISTER = 4; + const DEVICE_TYPE_PC_TERMINAL = 5; + const DEVICE_TYPE_AIRPAY = 6; + const DEVICE_TYPE_WIRELESS_POS = 7; + const DEVICE_TYPE_WEBSITE = 8; + const DEVICE_TYPE_DIAL_TERMINAL = 9; + const DEVICE_TYPE_VIRTUAL_TERMINAL = 10; + public function getName() { return 'Authorize.Net AIM'; @@ -29,7 +43,7 @@ public function getDefaultParameters() 'hashSecret' => '', 'liveEndpoint' => 'https://api2.authorize.net/xml/v1/request.api', 'developerEndpoint' => 'https://apitest.authorize.net/xml/v1/request.api', - 'deviceType' => 1 // Device used to make the transaction. Required for card present. "1" = Unknown. + 'deviceType' => static::DEVICE_TYPE_UNKNOWN ); } @@ -115,8 +129,8 @@ public function getDeviceType() } /** - * Sets the type of device used to collect the credit card data. A device type is required for card present - * transactions. + * Sets the type of device used to collect the credit card data. + * A device type is required for card present transactions. * * 1 = Unknown * 2 = Unattended Terminal diff --git a/src/Message/AIMAuthorizeRequest.php b/src/Message/AIMAuthorizeRequest.php index 9757910d..84f5732d 100644 --- a/src/Message/AIMAuthorizeRequest.php +++ b/src/Message/AIMAuthorizeRequest.php @@ -37,10 +37,12 @@ protected function addPayment(\SimpleXMLElement $data) return; } + // The CreditCard object must be present. $this->validate('card'); + /** @var CreditCard $card */ $card = $this->getCard(); - $card->validate(); + if ($card->getTracks()) { // Card present if ($track1 = $card->getTrack1()) { @@ -49,7 +51,11 @@ protected function addPayment(\SimpleXMLElement $data) $data->transactionRequest->payment->trackData->track2 = $track2; } } else { - // Card not present + // Card not present. + + // Validate sufficient card details have been supplied. + $card->validate(); + $data->transactionRequest->payment->creditCard->cardNumber = $card->getNumber(); $data->transactionRequest->payment->creditCard->expirationDate = $card->getExpiryDate('my'); $data->transactionRequest->payment->creditCard->cardCode = $card->getCvv(); diff --git a/tests/Message/AIMAuthorizeRequestTest.php b/tests/Message/AIMAuthorizeRequestTest.php index 3fcca4c4..91d3be79 100644 --- a/tests/Message/AIMAuthorizeRequestTest.php +++ b/tests/Message/AIMAuthorizeRequestTest.php @@ -96,6 +96,13 @@ public function testGetDataCardPresentTrack1() { $card = $this->getValidCard(); $card['tracks'] = '%B4242424242424242^SMITH/JOHN ^2511126100000000000000444000000?;4242424242424242=25111269999944401?'; + + // If sending tracks, then the card number and expiry date are not required. + unset($card['number']); + unset($card['expiryMonth']); + unset($card['expiryYear']); + unset($card['cvv']); + $this->request->initialize(array( 'amount' => '12.12', 'card' => $card,