Skip to content

Commit

Permalink
Merge pull request #23 from judgej/issue_19
Browse files Browse the repository at this point in the history
Issue 19/16/22
  • Loading branch information
greydnls committed Jul 15, 2015
2 parents 463cdc7 + e2c25ff commit 142a95f
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 151 deletions.
16 changes: 15 additions & 1 deletion src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
/**
* Authorize.Net Abstract Request
*/
abstract class AbstractRequest extends \Omnipay\Common\Message\AbstractRequest

use Omnipay\Common\Message\AbstractRequest as CommonAbstractRequest;

abstract class AbstractRequest extends CommonAbstractRequest
{
/**
* Custom field name to send the transaction ID to the notify handler.
*/
const TRANSACTION_ID_PARAM = 'omnipay_transaction_id';

public function getApiLoginId()
{
return $this->getParameter('apiLoginId');
Expand Down Expand Up @@ -99,7 +107,13 @@ protected function getBillingData()
{
$data = array();
$data['x_amount'] = $this->getAmount();

// This is deprecated. The invoice number field is reserved for the invoice number.
$data['x_invoice_num'] = $this->getTransactionId();

// A custom field can be used to pass over the merchant site transaction ID.
$data[static::TRANSACTION_ID_PARAM] = $this->getTransactionId();

$data['x_description'] = $this->getDescription();

if ($card = $this->getCard()) {
Expand Down
56 changes: 0 additions & 56 deletions src/Message/DPMCompleteRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,6 @@
*/
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);
Expand Down
74 changes: 2 additions & 72 deletions src/Message/DPMCompleteResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,9 @@

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).
* SIM and DPM both have identical needs when handling the notify request.
*/
class DPMCompleteResponse extends SIMCompleteAuthorizeResponse implements RedirectResponseInterface
class DPMCompleteResponse extends SIMCompleteAuthorizeResponse
{
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;
}
}
14 changes: 11 additions & 3 deletions src/Message/SIMAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ class SIMAuthorizeRequest extends AbstractRequest

public function getData()
{
$this->validate('amount', 'returnUrl');
$this->validate('amount');

// Either the nodifyUrl or the returnUrl can be provided.
// The returnUrl is deprecated, as strictly this is a notifyUrl.
if (!$this->getNotifyUrl()) {
$this->validate('returnUrl');
}

$data = array();
$data['x_login'] = $this->getApiLoginId();
Expand All @@ -28,9 +34,11 @@ public function getData()
$data['x_customer_ip'] = $this->getClientIp();
}

// The returnUrl MUST be set in Authorize.net admin panel under
// The returnUrl MUST be whitelisted in Authorize.net admin panel under
// "Response/Receipt URLs".
$data['x_relay_url'] = $this->getReturnUrl();
// Use the notifyUrl if available, as that is strictly what this is.
// Fall back to returnUrl for BC support.
$data['x_relay_url'] = $this->getNotifyUrl() ?: $this->getReturnUrl();
$data['x_cancel_url'] = $this->getCancelUrl();

if ($this->getCustomerId() !== null) {
Expand Down
54 changes: 51 additions & 3 deletions src/Message/SIMCompleteAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,53 @@
*/
class SIMCompleteAuthorizeRequest extends AbstractRequest
{
/**
* Get the transaction ID passed in through the custom field.
* This is used to look up the transaction in storage.
*/
public function getTransactionId()
{
return $this->httpRequest->request->get(static::TRANSACTION_ID_PARAM);
}

public function getData()
{
if (strtolower($this->httpRequest->request->get('x_MD5_Hash')) !== $this->getHash()) {
// 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->getHash($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();
}

Expand All @@ -23,9 +64,16 @@ public function getData()
* The transaction reference and the amount are both sent by the remote gateway (x_trans_id
* and x_amount) and it is those that should be checked against.
*/
public function getHash()
public function getHash($transaction_reference, $amount)
{
return md5($this->getHashSecret().$this->getApiLoginId().$this->getTransactionId().$this->getAmount());
$key = array(
$this->getHashSecret(),
$this->getApiLoginId(),
$transaction_reference,
$amount,
);

return md5(implode('', $key));
}

public function sendData($data)
Expand Down
86 changes: 84 additions & 2 deletions src/Message/SIMCompleteAuthorizeResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,33 @@
namespace Omnipay\AuthorizeNet\Message;

use Omnipay\Common\Message\AbstractResponse;
use Omnipay\Common\Message\RedirectResponseInterface;
use Symfony\Component\HttpFoundation\Response as HttpResponse;

/**
* Authorize.Net SIM Complete Authorize Response
*/
class SIMCompleteAuthorizeResponse extends AbstractResponse
class SIMCompleteAuthorizeResponse extends AbstractResponse implements RedirectResponseInterface
{
// Response codes returned by Authorize.Net

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']) && '1' === $this->data['x_response_code'];
return static::RESPONSE_CODE_APPROVED === $this->getCode();
}

/**
* 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 static::RESPONSE_CODE_ERROR === $this->getCode();
}

public function getTransactionReference()
Expand All @@ -33,4 +51,68 @@ public function getCode()
{
return isset($this->data['x_response_code']) ? $this->data['x_response_code'] : null;
}

/**
* This message is handled in a notify, where a HTML redirect must be performed.
*/
public function isRedirect()
{
return true;
}

/**
* The merchant site notify handler needs to set the returnUrl in the complete request.
*/
public function getRedirectUrl()
{
return $this->request->getReturnUrl();
}

public function getRedirectMethod()
{
return 'GET';
}

/**
* There is no redirect data to send; the aim is just to get the user to a URL
* by delivering a HTML page.
*/
public function getRedirectData()
{
return array();
}

/**
* Authorize.Net requires a redirect in a HTML page.
* The OmniPay redirect helper will only provide a HTML page for the POST method
* and then implements that through a self-submitting form, which will generate
* browser warnings if returning to a non-SSL page. This JavScript and meta refresh
* page avoids the security warning. No data is sent in this redirect, as that will
* have all been saved with the transaction in storage.
*/
public function getRedirectResponse()
{
$output = <<<ENDHTML
<!DOCTYPE html>
<html>
<head>
<title>Redirecting...</title>
<meta http-equiv="refresh" content="0;url=%1\$s" />
</head>
<body>
<p>Redirecting to <a href="%1\$s">payment complete page</a>...</p>
<script type="text/javascript" charset="utf-8">
window.location="%1\$s";
</script>
</body>
</html>
ENDHTML;

$output = sprintf(
$output,
htmlentities($this->getRedirectUrl(), ENT_QUOTES, 'UTF-8', false)
);

return HttpResponse::create($output);
}
}
Loading

0 comments on commit 142a95f

Please sign in to comment.