diff --git a/composer.json b/composer.json index 402d88fb..2cd0e1c0 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "psr-4": { "Omnipay\\AuthorizeNet\\" : "src/" } }, "require": { - "omnipay/common": "~2.2" + "omnipay/common": "~2.5" }, "require-dev": { "omnipay/tests": "~2.0" 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 207502bf..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 */ @@ -240,18 +250,23 @@ 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; } + + 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 919638e2..7df44c8d 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->addTransactionSettings($data); return $data; @@ -30,9 +31,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) @@ -42,4 +53,13 @@ protected function addCustomerIP(\SimpleXMLElement $data) $data->transactionRequest->customerIP = $ip; } } + + 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 = $this->getDeviceType(); + } + } } 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/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/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 73788561..2bc0b80a 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() @@ -54,4 +56,46 @@ public function testShouldIncludeDuplicateWindowSetting() $this->assertEquals('duplicateWindow', $setting->settingName); $this->assertEquals('0', $setting->settingValue); } + + public function testGetDataCardPresentTrack1() + { + $card = $this->getValidCard(); + $card['tracks'] = '%B4242424242424242^SMITH/JOHN ^2511126100000000000000444000000?;4242424242424242=25111269999944401?'; + $this->request->initialize(array( + 'amount' => '12.12', + 'card' => $card, + 'deviceType' => 1 + )); + + $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, + 'deviceType' => 1 + )); + + $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); + } } diff --git a/tests/Message/CIMAuthorizeRequestTest.php b/tests/Message/CIMAuthorizeRequestTest.php index 04d6e90e..136c57be 100644 --- a/tests/Message/CIMAuthorizeRequestTest.php +++ b/tests/Message/CIMAuthorizeRequestTest.php @@ -31,4 +31,19 @@ public function testGetData() $this->assertEquals('27057151', $data->transactionRequest->profile->shippingProfileId); $this->assertEquals('Test authorize transaction', $data->transactionRequest->order->description); } + + 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); + } }