Skip to content

Card present support for AIM & CIM #36

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

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"psr-4": { "Omnipay\\AuthorizeNet\\" : "src/" }
},
"require": {
"omnipay/common": "~2.2"
"omnipay/common": "~2.5"
},
"require-dev": {
"omnipay/tests": "~2.0"
Expand Down
30 changes: 30 additions & 0 deletions src/AIMGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
);
}

Expand Down Expand Up @@ -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
Expand Down
27 changes: 21 additions & 6 deletions src/Message/AIMAbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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();
}
}
26 changes: 23 additions & 3 deletions src/Message/AIMAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public function getData()
$this->addPayment($data);
$this->addCustomerIP($data);
$this->addBillingData($data);
$this->addRetail($data);
$this->addTransactionSettings($data);

return $data;
Expand All @@ -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)
Expand All @@ -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();
}
}
}
45 changes: 28 additions & 17 deletions src/Message/CIMAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
10 changes: 10 additions & 0 deletions src/Message/SIMAbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
17 changes: 17 additions & 0 deletions tests/AIMGatewayIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
44 changes: 44 additions & 0 deletions tests/Message/AIMAuthorizeRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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);
}
}
15 changes: 15 additions & 0 deletions tests/Message/CIMAuthorizeRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}