Skip to content

Commit

Permalink
Merge pull request #18 from academe/dpm_support
Browse files Browse the repository at this point in the history
DPM support for Authorize.Net
  • Loading branch information
greydnls committed May 12, 2015
2 parents 7b51669 + f83008e commit 463cdc7
Show file tree
Hide file tree
Showing 20 changed files with 933 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ The following gateways are provided by this package:

* AuthorizeNet_AIM
* AuthorizeNet_SIM
* AuthorizeNet_DPM

For general usage instructions, please see the main [Omnipay](https://github.com/thephpleague/omnipay)
repository.
Expand Down
36 changes: 32 additions & 4 deletions src/AIMGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ public function getName()
public function getDefaultParameters()
{
return array(
'apiLoginId' => '',
'transactionKey' => '',
'testMode' => false,
'developerMode' => false,
'apiLoginId' => '',
'transactionKey' => '',
'testMode' => false,
'developerMode' => false,
'liveEndpoint' => 'https://secure.authorize.net/gateway/transact.dll',
'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll',
);
}

Expand Down Expand Up @@ -54,6 +56,32 @@ public function setDeveloperMode($value)
return $this->setParameter('developerMode', $value);
}

public function setEndpoints($endpoints)
{
$this->setParameter('liveEndpoint', $endpoints['live']);
return $this->setParameter('developerEndpoint', $endpoints['developer']);
}

public function getLiveEndpoint()
{
return $this->getParameter('liveEndpoint');
}

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

public function getDeveloperEndpoint()
{
return $this->getParameter('developerEndpoint');
}

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

public function authorize(array $parameters = array())
{
return $this->createRequest('\Omnipay\AuthorizeNet\Message\AIMAuthorizeRequest', $parameters);
Expand Down
46 changes: 46 additions & 0 deletions src/DPMGateway.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Omnipay\AuthorizeNet;

/**
* Authorize.Net DPM (Direct Post Method) Class
*/
class DPMGateway extends SIMGateway
{
public function getName()
{
return 'Authorize.Net DPM';
}

/**
* Helper to generate the authorize direct-post form.
*/
public function authorize(array $parameters = array())
{
return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMAuthorizeRequest', $parameters);
}

/**
* Get, validate, interpret and respond to the Authorize.Net callback.
*/
public function completeAuthorize(array $parameters = array())
{
return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteRequest', $parameters);
}

/**
* Helper to generate the purchase direct-post form.
*/
public function purchase(array $parameters = array())
{
return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMPurchaseRequest', $parameters);
}

/**
* Get, validate, interpret and respond to the Authorize.Net callback.
*/
public function completePurchase(array $parameters = array())
{
return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteRequest', $parameters);
}
}
32 changes: 28 additions & 4 deletions src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
*/
abstract class AbstractRequest extends \Omnipay\Common\Message\AbstractRequest
{
protected $liveEndpoint = 'https://secure.authorize.net/gateway/transact.dll';
protected $developerEndpoint = 'https://test.authorize.net/gateway/transact.dll';

public function getApiLoginId()
{
return $this->getParameter('apiLoginId');
Expand Down Expand Up @@ -60,6 +57,29 @@ public function setHashSecret($value)
return $this->setParameter('hashSecret', $value);
}

public function getLiveEndpoint()
{
return $this->getParameter('liveEndpoint');
}

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

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

public function getDeveloperEndpoint()
{
return $this->getParameter('developerEndpoint');
}

/**
* Base data used only for the AIM API.
*/
protected function getBaseData()
{
$data = array();
Expand Down Expand Up @@ -124,6 +144,10 @@ public function sendData($data)

public function getEndpoint()
{
return $this->getDeveloperMode() ? $this->developerEndpoint : $this->liveEndpoint;
if ($this->getDeveloperMode()) {
return $this->getParameter('developerEndpoint');
} else {
return $this->getParameter('liveEndpoint');
}
}
}
61 changes: 61 additions & 0 deletions src/Message/DPMAuthorizeRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Omnipay\AuthorizeNet\Message;

/**
* Authorize.Net DPM Authorize Request.
* Takes the data that will be used to create the direct-post form.
*/
class DPMAuthorizeRequest extends SIMAuthorizeRequest
{
protected $action = 'AUTH_ONLY';

public function getData()
{
$data = parent::getData();

// If x_show_form is set, then the form will be displayed on the Authorize.Net
// gateway, in a similar way to the SIM gateway. The DPM documentation does NOT
// make this clear at all.
// Since x_show_form is set in the SIM gateway, make sure we unset it here.

unset($data['x_show_form']);

// Must be set for DPM.
// This directs all errors to the relay response.

$data['x_relay_always'] = 'TRUE';

// The card details are optional.
// They will most likely only be used for development and testing.
// The card fields are still needed in the direct-post form regardless.

if ($this->getCard()) {
$data['x_card_num'] = $this->getCard()->getNumber();

// Workaround for https://github.com/thephpleague/omnipay-common/issues/29
$expiry_date = $this->getCard()->getExpiryDate('my');
$data['x_exp_date'] = ($expiry_date === '1299' ? '' : $expiry_date);

$data['x_card_code'] = $this->getCard()->getCvv();
} else {
$data['x_card_num'] = '';
$data['x_exp_date'] = '';
$data['x_card_code'] = '';
}

return $data;
}


/**
* Given the DPM data, we want to turn it into a form for the user to submit to Authorize.net
* The form may have most of the fields hidden, or may allow the user to change some details -
* that depends on the use-case.
* So this method will provide us with an object used to build the form.
*/
public function sendData($data)
{
return $this->response = new DPMResponse($this, $data, $this->getEndpoint());
}
}
72 changes: 72 additions & 0 deletions src/Message/DPMCompleteRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Omnipay\AuthorizeNet\Message;

use Omnipay\Common\Exception\InvalidRequestException;

/**
* Authorize.Net DPM Complete Authorize Request
*/
class DPMCompleteRequest extends SIMCompleteAuthorizeRequest
{
public function getData()
{
// The hash sent in the callback from the Authorize.Net gateway.
$hash_posted = strtolower($this->httpRequest->request->get('x_MD5_Hash'));

// The transaction reference generated by the Authorize.Net gateway and sent in the callback.
$posted_transaction_reference = $this->httpRequest->request->get('x_trans_id');

// The amount that the callback has authorized.
$posted_amount = $this->httpRequest->request->get('x_amount');

// Calculate the hash locally, using the shared "hash secret" and login ID.
$hash_calculated = $this->getDpmHash($posted_transaction_reference, $posted_amount);

if ($hash_posted !== $hash_calculated) {
// If the hash is incorrect, then we can't trust the source nor anything sent.
// Throwing exceptions here is probably a bad idea. We are trying to get the data,
// and if it is invalid, then we need to be able to log that data for analysis.
// Except we can't, baceuse the exception means we can't get to the data.
// For now, this is consistent with other OmniPay gateway drivers.

throw new InvalidRequestException('Incorrect hash');
}

// The hashes have passed, but the amount should also be validated against the
// amount in the stored and retrieved transaction. If the application has the
// ability to retrieve the transaction (using the transaction_id sent as a custom
// form field, or perhaps in an otherwise unused field such as x_invoice_id.

$amount = $this->getAmount();

if (isset($amount) && $amount != $posted_amount) {
// The amounts don't match. Someone may have been playing with the
// transaction references.

throw new InvalidRequestException('Incorrect amount');
}

return $this->httpRequest->request->all();
}

/**
* This hash confirms the ransaction has come from the Authorize.Net gateway.
* It confirms the sender knows ther shared hash secret and that the amount and
* transaction reference has not been changed in transit.
*/
public function getDpmHash($transaction_reference, $amount)
{
$key = $this->getHashSecret()
. $this->getApiLoginId()
. $transaction_reference
. $amount;

return md5($key);
}

public function sendData($data)
{
return $this->response = new DPMCompleteResponse($this, $data);
}
}
80 changes: 80 additions & 0 deletions src/Message/DPMCompleteResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace Omnipay\AuthorizeNet\Message;

use Omnipay\Common\Message\AbstractResponse;
use Omnipay\Common\Message\RedirectResponseInterface;

/**
* Authorize.Net DPM Complete Authorize Response
* This is the result of handling the callback.
* The result will always be a HTML redirect snippet. This gets
* returned to the gateway, displayed in the user's browser, and a
* redirect is performed using JavaScript and meta refresh (for backup).
* We may want to return to the success page, the failed page or the retry
* page (so the user can correct the form to try again).
*/
class DPMCompleteResponse extends SIMCompleteAuthorizeResponse implements RedirectResponseInterface
{
const RESPONSE_CODE_APPROVED = '1';
const RESPONSE_CODE_DECLINED = '2';
const RESPONSE_CODE_ERROR = '3';
const RESPONSE_CODE_REVIEW = '4';

public function isSuccessful()
{
return isset($this->data['x_response_code'])
&& static::RESPONSE_CODE_APPROVED === $this->data['x_response_code'];
}

/**
* If there is an error in the form, then the user should be able to go back
* to the form and give it another shot.
*/
public function isError()
{
return isset($this->data['x_response_code'])
&& static::RESPONSE_CODE_ERROR === $this->data['x_response_code'];
}

/**
* We are in the callback, and we MUST return a HTML fragment to do a redirect.
* All headers we may return are discarded by the gateway, so we cannot use
* the "Location:" header.
*/
public function isRedirect()
{
return true;
}

/**
* We set POST because the default redirect mechanism in Omnipay Common only
* generates a HTML snippet for POST and not for the GET method.
* The redirect method is actually "HTML", where a HTML page is supplied
* to do a redirect using any method it likes.
*/
public function getRedirectMethod()
{
return 'POST';
}

/**
* We probably do not require any redirect data, if the incomplete transaction
* is still in the user's session and we can inspect the results from the saved
* transaction in the database. We cannot send the result through the redirect
* unless it is hashed so the authorisation result cannot be faked.
*/
public function getRedirectData()
{
return array();
}

/**
* The cancel URL is never handled here - that is a direct link from the gateway.
*/
public function getRedirectUrl()
{
// Leave it for the applicatino to decide where to sent the user.
return;
}
}
11 changes: 11 additions & 0 deletions src/Message/DPMPurchaseRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Omnipay\AuthorizeNet\Message;

/**
* Authorize.Net DPM Purchase Request (aka "Authorize and Capture")
*/
class DPMPurchaseRequest extends DPMAuthorizeRequest
{
protected $action = 'AUTH_CAPTURE';
}
Loading

0 comments on commit 463cdc7

Please sign in to comment.