From 0c106c64f3658fcd9dde3967c19b09f59e1b4f45 Mon Sep 17 00:00:00 2001 From: Tristan Harris Date: Fri, 19 Dec 2014 15:09:32 +0000 Subject: [PATCH 01/27] Allow alternative endpoint urls --- src/AIMGateway.php | 8 ++++++++ src/Message/AbstractRequest.php | 15 +++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/AIMGateway.php b/src/AIMGateway.php index 990c5928..ff88e93c 100644 --- a/src/AIMGateway.php +++ b/src/AIMGateway.php @@ -24,6 +24,8 @@ public function getDefaultParameters() 'transactionKey' => '', 'testMode' => false, 'developerMode' => false, + 'liveEndpoint' => 'https://secure.authorize.net/gateway/transact.dll', + 'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll' ); } @@ -57,6 +59,12 @@ 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 authorize(array $parameters = array()) { return $this->createRequest('\Omnipay\AuthorizeNet\Message\AIMAuthorizeRequest', $parameters); diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index a8d45f19..18d11a7f 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -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'); @@ -60,6 +57,16 @@ public function setHashSecret($value) return $this->setParameter('hashSecret', $value); } + public function setLiveEndpoint($value) + { + return $this->setParameter('liveEndpoint', $value); + } + + public function setDeveloperEndpoint($value) + { + return $this->setParameter('developerEndpoint', $value); + } + protected function getBaseData() { $data = array(); @@ -124,6 +131,6 @@ public function sendData($data) public function getEndpoint() { - return $this->getDeveloperMode() ? $this->developerEndpoint : $this->liveEndpoint; + return $this->getDeveloperMode() ? $this->getParameter('developerEndpoint') : $this->getParameter('liveEndpoint'); } } From 4423c883f692f2f2508b236866ddd3e05fe0db2f Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Tue, 3 Feb 2015 23:03:20 +0000 Subject: [PATCH 02/27] Started adding DPM gateway. --- src/DPMGateway.php | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/DPMGateway.php diff --git a/src/DPMGateway.php b/src/DPMGateway.php new file mode 100644 index 00000000..b3d4891e --- /dev/null +++ b/src/DPMGateway.php @@ -0,0 +1,10 @@ + Date: Wed, 4 Feb 2015 02:10:06 +0000 Subject: [PATCH 03/27] Dev snapshot. --- src/DPMGateway.php | 11 ++++- src/Message/DPMAuthorizeRequest.php | 72 ++++++++++++++++++++++++++++ src/Message/DPMAuthorizeResponse.php | 35 ++++++++++++++ src/Message/DPMPurchaseRequest.php | 11 +++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 src/Message/DPMAuthorizeRequest.php create mode 100644 src/Message/DPMAuthorizeResponse.php create mode 100644 src/Message/DPMPurchaseRequest.php diff --git a/src/DPMGateway.php b/src/DPMGateway.php index b3d4891e..9867e261 100644 --- a/src/DPMGateway.php +++ b/src/DPMGateway.php @@ -5,6 +5,15 @@ /** * Authorize.Net DPM (Direct Post Method) Class */ -class DPMGateway extends AIMGateway +class DPMGateway extends SIMGateway { + public function getName() + { + return 'Authorize.Net DPM'; + } + + public function authorize(array $parameters = array()) + { + return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMAuthorizeRequest', $parameters); + } } diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php new file mode 100644 index 00000000..e6858c66 --- /dev/null +++ b/src/Message/DPMAuthorizeRequest.php @@ -0,0 +1,72 @@ +getApiLoginId(), + $data['x_fp_sequence'], + $data['x_fp_timestamp'], + $data['x_amount'] + ) + ).'^'; + + // If x_currency_code is specified, then it must follow the final trailing carat. + // CHECKME: this may need to be back-ported to SIMAuthorizeRequest and AIMAuthorizeRequest + // in order to supprot multiple currencies. + + if ($this->getCurrency()) { + $fingerprint .= $this->getCurrency(); + } + + return hash_hmac('md5', $fingerprint, $this->getTransactionKey()); + } + + public function getData() + { + $data = parent::getData(); + + // This is the DPM trigger. + $data['x_show_form'] = 'PAYMENT_FORM'; + + // Support multiple currencies. + // CHECKME: should this be back-ported to SIMAuthorizeRequest and AIMAuthorizeRequest? + + if ($this->getCurrency()) { + $data['x_currency_code'] = $this->getCurrency(); + } + + // CHECKME: x_recurring_billing is (ambiguously) listed as mandatory in the DPM docs. + + // The customer ID is optional. + if ($this->getCustomerId()) { + $data['x_cust_id'] = $this->getCustomerId(); + } + + 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 DPMAuthorizeResponse($this, $data, $this->getEndpoint()); + } +} diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMAuthorizeResponse.php new file mode 100644 index 00000000..fa89ab62 --- /dev/null +++ b/src/Message/DPMAuthorizeResponse.php @@ -0,0 +1,35 @@ +request = $request; + $this->data = $data; + $this->postUrl = $postUrl; + } + + public function isSuccessful() + { + return true; + } + + public function getPostUrl() + { + return $this->postUrl; + } + + // Testing: use getData() to look at what we have. +} diff --git a/src/Message/DPMPurchaseRequest.php b/src/Message/DPMPurchaseRequest.php new file mode 100644 index 00000000..eeaf9373 --- /dev/null +++ b/src/Message/DPMPurchaseRequest.php @@ -0,0 +1,11 @@ + Date: Thu, 5 Feb 2015 01:28:06 +0000 Subject: [PATCH 04/27] Dev snapshot. --- README.md | 1 + src/Message/DPMAuthorizeRequest.php | 4 ++ src/Message/DPMAuthorizeResponse.php | 75 +++++++++++++++++++++++++++- src/Message/SIMAuthorizeRequest.php | 2 + 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2845c896..5f569648 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index e6858c66..7d28a137 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -55,6 +55,10 @@ public function getData() $data['x_cust_id'] = $this->getCustomerId(); } + $data['x_card_num'] = $this->getCard()->getNumber(); + $data['x_exp_date'] = $this->getCard()->getExpiryDate('my'); + $data['x_card_code'] = $this->getCard()->getCvv(); + return $data; } diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMAuthorizeResponse.php index fa89ab62..a8ad31f8 100644 --- a/src/Message/DPMAuthorizeResponse.php +++ b/src/Message/DPMAuthorizeResponse.php @@ -14,6 +14,23 @@ class DPMAuthorizeResponse extends AbstractResponse { protected $postUrl; + protected $hiddenFields = array( + 'x_fp_hash', + 'x_amount', + 'x_test_request', + 'x_cancel_url', + 'x_relay_url', + 'x_relay_response', + 'x_show_form', + 'x_delim_data', + 'x_fp_timestamp', + 'x_fp_sequence', + 'x_type', + 'x_login', + 'x_invoice_num', + 'x_description', + ); + public function __construct(RequestInterface $request, $data, $postUrl) { $this->request = $request; @@ -21,15 +38,71 @@ public function __construct(RequestInterface $request, $data, $postUrl) $this->postUrl = $postUrl; } + /** + * Return false to indicate that more action is needed to complete + * the transaction, a transparent redirect form in this case. + */ public function isSuccessful() + { + return false; + } + + /** + * This is a transparent redirect transaction type, where a local form + * will POST direct to the remote gateway. + */ + public function isTransparentRedirect() { return true; } + // Helpers to build the form. + + /** + * The URL the form will be posted to. + */ public function getPostUrl() { return $this->postUrl; } - // Testing: use getData() to look at what we have. + /** + * Add a field to the list of hidden fields. + * The hidden fields are those we don't want to show the user, but + * must still be posted. + */ + public function setHiddenField($field_name) + { + if (!in_array($field_name, $this->hiddenFields)) { + $this->hiddenFields[] = $field_name; + } + } + + /** + * Remove a field from the list of hidden fields. + */ + public function unsetHiddenField($field_name) + { + if (($key = array_search($field_name, $this->hiddenFields)) !== false) { + unset($this->hiddenFields[$key]); + } + } + + /** + * Data that must be included as hidden fields, if they are available at all. + */ + public function getHiddenData() + { + return array_intersect_key($this->getData(), array_flip($this->hiddenFields)); + } + + /** + * Data not in the hidden fields list. + * These are not all mandatory, so you do not have to present all these + * to the user. + */ + public function getNonHiddenData() + { + return array_diff_key($this->getData(), array_flip($this->hiddenFields)); + } } diff --git a/src/Message/SIMAuthorizeRequest.php b/src/Message/SIMAuthorizeRequest.php index fc877eb0..292c099d 100644 --- a/src/Message/SIMAuthorizeRequest.php +++ b/src/Message/SIMAuthorizeRequest.php @@ -21,6 +21,8 @@ public function getData() $data['x_delim_data'] = 'FALSE'; $data['x_show_form'] = 'PAYMENT_FORM'; $data['x_relay_response'] = 'TRUE'; + // The returnUrl MUST be set in Authorize.net admin panel as a + // "Response/Receipt URLs" URL, but not necessarily the default. $data['x_relay_url'] = $this->getReturnUrl(); $data['x_cancel_url'] = $this->getCancelUrl(); From 1040f537f6af0f7c1f404562112e25942472d913 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Thu, 5 Feb 2015 20:03:48 +0000 Subject: [PATCH 05/27] Dev snapshot. --- src/DPMGateway.php | 9 +++ src/Message/DPMCompleteAuthorizeRequest.php | 64 ++++++++++++++++++++ src/Message/DPMCompleteAuthorizeResponse.php | 40 ++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/Message/DPMCompleteAuthorizeRequest.php create mode 100644 src/Message/DPMCompleteAuthorizeResponse.php diff --git a/src/DPMGateway.php b/src/DPMGateway.php index 9867e261..cc4278af 100644 --- a/src/DPMGateway.php +++ b/src/DPMGateway.php @@ -12,8 +12,17 @@ public function getName() return 'Authorize.Net DPM'; } +/* public function setHashSecret() + { + } */ + public function authorize(array $parameters = array()) { return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMAuthorizeRequest', $parameters); } + + public function completeAuthorize(array $parameters = array()) + { + return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteAuthorizeRequest', $parameters); + } } diff --git a/src/Message/DPMCompleteAuthorizeRequest.php b/src/Message/DPMCompleteAuthorizeRequest.php new file mode 100644 index 00000000..49e0b83f --- /dev/null +++ b/src/Message/DPMCompleteAuthorizeRequest.php @@ -0,0 +1,64 @@ +httpRequest->request->get('x_MD5_Hash')); + $posted_transaction_reference = $this->httpRequest->request->get('x_trans_id'); + $posted_amount = $this->httpRequest->request->get('x_amount'); + $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. + + 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 as a GET parameter on the callback URL) then it will + // be checked here. + + $amount = $this->getAmount(); + + if (isset($amount) && $amount != $posted_amount) { + // The amounts don't match up. 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 basically tests the shared hash secret is correct, but mixes in other details + * that will change for each transaction so the hash will be unique for each transaction. + * The hash secret and login ID are known to the merchent site, and the amount and transaction + * reference (x_amount and x_trans_id) are sent by the gatewa. + */ + public function getDpmHash($transaction_reference, $amount) + { + return md5( + $this->getHashSecret() + . $this->getApiLoginId() + . $transaction_reference + . $amount + ); + } + + public function sendData($data) + { + return $this->response = new DPMCompleteAuthorizeResponse($this, $data); + } +} diff --git a/src/Message/DPMCompleteAuthorizeResponse.php b/src/Message/DPMCompleteAuthorizeResponse.php new file mode 100644 index 00000000..0e288749 --- /dev/null +++ b/src/Message/DPMCompleteAuthorizeResponse.php @@ -0,0 +1,40 @@ +data['x_response_code']) && static::RESPONSE_CODE_APPROVED === $this->data['x_response_code']; + } + + public function getTransactionReference() + { + return isset($this->data['x_trans_id']) ? $this->data['x_trans_id'] : null; + } + + // TODO: getCode()? + // TODO: redirect details (need "reptry" URL too). + + public function getMessage() + { + return isset($this->data['x_response_reason_text']) ? $this->data['x_response_reason_text'] : null; + } +} From ac397199d765b52654cf88722f278c5d1b9cf6ed Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Fri, 6 Feb 2015 13:46:51 +0000 Subject: [PATCH 06/27] Dev snapshot --- src/Message/DPMCompleteAuthorizeResponse.php | 67 ++++++++++++++++---- src/Message/SIMCompleteAuthorizeResponse.php | 10 +++ 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/Message/DPMCompleteAuthorizeResponse.php b/src/Message/DPMCompleteAuthorizeResponse.php index 0e288749..ea55bb27 100644 --- a/src/Message/DPMCompleteAuthorizeResponse.php +++ b/src/Message/DPMCompleteAuthorizeResponse.php @@ -3,6 +3,7 @@ namespace Omnipay\AuthorizeNet\Message; use Omnipay\Common\Message\AbstractResponse; +use Omnipay\Common\Message\RedirectResponseInterface; /** * Authorize.Net DPM Complete Authorize Response @@ -13,28 +14,72 @@ * We may want to return to the success page, the failed page or the retry * page (so the user can correct the form). */ -class DPMCompleteAuthorizeResponse extends SIMCompleteAuthorizeResponse // TOOD: redirect +class DPMCompleteAuthorizeResponse extends SIMCompleteAuthorizeResponse implements RedirectResponseInterface { - const RESPONSE_CODE_APPROVED = '1'; - const RESPONSE_CODE_DECLINED = '2'; - const RESPONSE_CODE_ERROR = '3'; - const RESPONSE_CODE_REVIEW = '4'; + 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']; } - public function getTransactionReference() + /** + * 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_trans_id']) ? $this->data['x_trans_id'] : null; + return isset($this->data['x_response_code']) && static::RESPONSE_CODE_ERROR === $this->data['x_response_code']; } - // TODO: getCode()? - // TODO: redirect details (need "reptry" URL too). - public function getMessage() { - return isset($this->data['x_response_reason_text']) ? $this->data['x_response_reason_text'] : null; + return parent::getReasonCode() . '|' . parent::getMessage(); + } + + /** + * 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 default here to POST because the default redirect mechanism + * in Omnipay Common only generates a HTML snippet for POST and not + * GET. + * TODO: We could fix that here so both GET and POST can be supported. + * Our fix should also include the "form data" with the URL. + */ + 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 in some way 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. + * The best approach is to have just one redirect URL, and once there, check the + * result of the authorisation in the database (assuming it has been saved in the + * callback) and take action from there. + */ + public function getRedirectUrl() + { } } diff --git a/src/Message/SIMCompleteAuthorizeResponse.php b/src/Message/SIMCompleteAuthorizeResponse.php index 1157121d..1908b0e8 100644 --- a/src/Message/SIMCompleteAuthorizeResponse.php +++ b/src/Message/SIMCompleteAuthorizeResponse.php @@ -23,4 +23,14 @@ public function getMessage() { return isset($this->data['x_response_reason_text']) ? $this->data['x_response_reason_text'] : null; } + + public function getReasonCode() + { + return isset($this->data['x_response_reason_code']) ? $this->data['x_response_reason_code'] : null; + } + + public function getCode() + { + return isset($this->data['x_response_code']) ? $this->data['x_response_code'] : null; + } } From 9e09dc181940ecfd186634baf0051beadfd91d69 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sun, 8 Feb 2015 16:49:27 +0000 Subject: [PATCH 07/27] Added Payment service. --- src/DPMGateway.php | 16 +++++++++++----- src/Message/DPMAuthorizeRequest.php | 9 ++++++--- src/Message/DPMAuthorizeResponse.php | 16 ++++++++++++++-- ...thorizeRequest.php => DPMCompleteRequest.php} | 4 ++-- ...orizeResponse.php => DPMCompleteResponse.php} | 2 +- src/Message/DPMPaymentRequest.php | 13 +++++++++++++ 6 files changed, 47 insertions(+), 13 deletions(-) rename src/Message/{DPMCompleteAuthorizeRequest.php => DPMCompleteRequest.php} (93%) rename src/Message/{DPMCompleteAuthorizeResponse.php => DPMCompleteResponse.php} (96%) create mode 100644 src/Message/DPMPaymentRequest.php diff --git a/src/DPMGateway.php b/src/DPMGateway.php index cc4278af..ecf6f7dd 100644 --- a/src/DPMGateway.php +++ b/src/DPMGateway.php @@ -12,10 +12,6 @@ public function getName() return 'Authorize.Net DPM'; } -/* public function setHashSecret() - { - } */ - public function authorize(array $parameters = array()) { return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMAuthorizeRequest', $parameters); @@ -23,6 +19,16 @@ public function authorize(array $parameters = array()) public function completeAuthorize(array $parameters = array()) { - return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteAuthorizeRequest', $parameters); + return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteRequest', $parameters); + } + + public function payment(array $parameters = array()) + { + return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMPaymentRequest', $parameters); + } + + public function completePayment(array $parameters = array()) + { + return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteRequest', $parameters); } } diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index 7d28a137..586c42b3 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -55,9 +55,12 @@ public function getData() $data['x_cust_id'] = $this->getCustomerId(); } - $data['x_card_num'] = $this->getCard()->getNumber(); - $data['x_exp_date'] = $this->getCard()->getExpiryDate('my'); - $data['x_card_code'] = $this->getCard()->getCvv(); + // The card details at this point are optional. + if ($this->getCard()) { + $data['x_card_num'] = $this->getCard()->getNumber(); + $data['x_exp_date'] = $this->getCard()->getExpiryDate('my'); + $data['x_card_code'] = $this->getCard()->getCvv(); + } return $data; } diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMAuthorizeResponse.php index a8ad31f8..28716995 100644 --- a/src/Message/DPMAuthorizeResponse.php +++ b/src/Message/DPMAuthorizeResponse.php @@ -4,13 +4,14 @@ use Omnipay\Common\Message\AbstractResponse; use Omnipay\Common\Message\RequestInterface; +use Omnipay\Common\Message\RedirectResponseInterface; /** * Authorize.Net DPM Authorize Response * Here we want the application to present a POST form to the user. This object will * provide the helper methods for doing so. */ -class DPMAuthorizeResponse extends AbstractResponse +class DPMAuthorizeResponse extends AbstractResponse implements RedirectResponseInterface { protected $postUrl; @@ -61,11 +62,22 @@ public function isTransparentRedirect() /** * The URL the form will be posted to. */ - public function getPostUrl() + public function getRedirectUrl() { return $this->postUrl; } + public function getRedirectMethod() + { + return "post"; + } + + // CHECKME: do we still need getHiddenData()? + public function getRedirectData() + { + return $this->getHiddenData(); + } + /** * Add a field to the list of hidden fields. * The hidden fields are those we don't want to show the user, but diff --git a/src/Message/DPMCompleteAuthorizeRequest.php b/src/Message/DPMCompleteRequest.php similarity index 93% rename from src/Message/DPMCompleteAuthorizeRequest.php rename to src/Message/DPMCompleteRequest.php index 49e0b83f..8b7007bd 100644 --- a/src/Message/DPMCompleteAuthorizeRequest.php +++ b/src/Message/DPMCompleteRequest.php @@ -7,7 +7,7 @@ /** * Authorize.Net DPM Complete Authorize Request */ -class DPMCompleteAuthorizeRequest extends SIMCompleteAuthorizeRequest +class DPMCompleteRequest extends SIMCompleteAuthorizeRequest { public function getData() { @@ -59,6 +59,6 @@ public function getDpmHash($transaction_reference, $amount) public function sendData($data) { - return $this->response = new DPMCompleteAuthorizeResponse($this, $data); + return $this->response = new DPMCompleteResponse($this, $data); } } diff --git a/src/Message/DPMCompleteAuthorizeResponse.php b/src/Message/DPMCompleteResponse.php similarity index 96% rename from src/Message/DPMCompleteAuthorizeResponse.php rename to src/Message/DPMCompleteResponse.php index ea55bb27..dda7164e 100644 --- a/src/Message/DPMCompleteAuthorizeResponse.php +++ b/src/Message/DPMCompleteResponse.php @@ -14,7 +14,7 @@ * We may want to return to the success page, the failed page or the retry * page (so the user can correct the form). */ -class DPMCompleteAuthorizeResponse extends SIMCompleteAuthorizeResponse implements RedirectResponseInterface +class DPMCompleteResponse extends SIMCompleteAuthorizeResponse implements RedirectResponseInterface { const RESPONSE_CODE_APPROVED = '1'; const RESPONSE_CODE_DECLINED = '2'; diff --git a/src/Message/DPMPaymentRequest.php b/src/Message/DPMPaymentRequest.php new file mode 100644 index 00000000..17e9b753 --- /dev/null +++ b/src/Message/DPMPaymentRequest.php @@ -0,0 +1,13 @@ + Date: Mon, 9 Feb 2015 15:32:05 +0000 Subject: [PATCH 08/27] Pass Customer Id as x_cust_id if provided --- src/Message/SIMAuthorizeRequest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Message/SIMAuthorizeRequest.php b/src/Message/SIMAuthorizeRequest.php index fc877eb0..91e2f727 100644 --- a/src/Message/SIMAuthorizeRequest.php +++ b/src/Message/SIMAuthorizeRequest.php @@ -23,6 +23,9 @@ public function getData() $data['x_relay_response'] = 'TRUE'; $data['x_relay_url'] = $this->getReturnUrl(); $data['x_cancel_url'] = $this->getCancelUrl(); + if ($this->getCustomerId() !== null) { + $data['x_cust_id'] = $this->getCustomerId(); + } if ($this->getTestMode()) { $data['x_test_request'] = 'TRUE'; From 5011207bcaaa5b6af24cad1a43786a6488b25552 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Tue, 10 Feb 2015 00:50:02 +0000 Subject: [PATCH 09/27] Dev snapshot. --- src/Message/DPMCompleteRequest.php | 11 +++++++---- src/Message/DPMCompleteResponse.php | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Message/DPMCompleteRequest.php b/src/Message/DPMCompleteRequest.php index 8b7007bd..836f3740 100644 --- a/src/Message/DPMCompleteRequest.php +++ b/src/Message/DPMCompleteRequest.php @@ -18,6 +18,9 @@ public function getData() if ($hash_posted !== $hash_calculated) { // If the hash is incorrect, then we can't trust the source nor anything sent. + // Throwing exceptions here is a *really* 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. throw new InvalidRequestException('Incorrect hash'); } @@ -49,12 +52,12 @@ public function getData() */ public function getDpmHash($transaction_reference, $amount) { - return md5( - $this->getHashSecret() + $key = $this->getHashSecret() . $this->getApiLoginId() . $transaction_reference - . $amount - ); + . $amount; + + return md5($key); } public function sendData($data) diff --git a/src/Message/DPMCompleteResponse.php b/src/Message/DPMCompleteResponse.php index dda7164e..cb077291 100644 --- a/src/Message/DPMCompleteResponse.php +++ b/src/Message/DPMCompleteResponse.php @@ -81,5 +81,7 @@ public function getRedirectData() */ public function getRedirectUrl() { + // Leave it for the applicatino to decide where to sent the user. + return; } } From 701f6fd53b07d88d90eae85f4c80edc97e5d3e3a Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Tue, 10 Feb 2015 16:39:44 +0000 Subject: [PATCH 10/27] Remove the x_show_form setting so no form is shown with DPM. --- src/Message/DPMAuthorizeRequest.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index 586c42b3..355225f0 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -38,8 +38,14 @@ public function getData() { $data = parent::getData(); - // This is the DPM trigger. - $data['x_show_form'] = 'PAYMENT_FORM'; + // If x_show_form is swet, then the form will be displayed on the Authorize.Net + // gateway, which acts a bit like the SIM gateway. The documentation does NOT + // make this clear. + // TODO: revisit this - maybe much of what is in the DPM can be used to enhance + // the SIM gateway, with very little in the DPM messages. + + //$data['x_show_form'] = 'PAYMENT_FORM'; + unset($data['x_show_form']); // Support multiple currencies. // CHECKME: should this be back-ported to SIMAuthorizeRequest and AIMAuthorizeRequest? From 4a391cd6440d9310d34d2e9d9de3093c9fdfa0c9 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sun, 15 Feb 2015 12:06:06 +0000 Subject: [PATCH 11/27] Remove "Payment" (should have been "Purchase") --- src/DPMGateway.php | 6 +++--- src/Message/DPMPaymentRequest.php | 13 ------------- src/Message/DPMPurchaseRequest.php | 2 +- 3 files changed, 4 insertions(+), 17 deletions(-) delete mode 100644 src/Message/DPMPaymentRequest.php diff --git a/src/DPMGateway.php b/src/DPMGateway.php index ecf6f7dd..85a3c72f 100644 --- a/src/DPMGateway.php +++ b/src/DPMGateway.php @@ -22,12 +22,12 @@ public function completeAuthorize(array $parameters = array()) return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteRequest', $parameters); } - public function payment(array $parameters = array()) + public function purchase(array $parameters = array()) { - return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMPaymentRequest', $parameters); + return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMPurchaseRequest', $parameters); } - public function completePayment(array $parameters = array()) + public function completePurchase(array $parameters = array()) { return $this->createRequest('\Omnipay\AuthorizeNet\Message\DPMCompleteRequest', $parameters); } diff --git a/src/Message/DPMPaymentRequest.php b/src/Message/DPMPaymentRequest.php deleted file mode 100644 index 17e9b753..00000000 --- a/src/Message/DPMPaymentRequest.php +++ /dev/null @@ -1,13 +0,0 @@ - Date: Sun, 15 Feb 2015 12:54:01 +0000 Subject: [PATCH 12/27] Backported DPM getHash() to SIM getHash() to support a specified currency. --- src/DPMGateway.php | 12 ++++++++++++ src/Message/DPMAuthorizeRequest.php | 26 ++------------------------ src/Message/SIMAuthorizeRequest.php | 11 +++++++++++ 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/DPMGateway.php b/src/DPMGateway.php index 85a3c72f..b5117ddf 100644 --- a/src/DPMGateway.php +++ b/src/DPMGateway.php @@ -12,21 +12,33 @@ 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); diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index 355225f0..15331049 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -5,35 +5,13 @@ use Omnipay\AuthorizeNet\Message\SIMAbstractRequest; /** - * Authorize.Net DPM Authorize Request + * 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 getHash($data) - { - $fingerprint = implode( - '^', - array( - $this->getApiLoginId(), - $data['x_fp_sequence'], - $data['x_fp_timestamp'], - $data['x_amount'] - ) - ).'^'; - - // If x_currency_code is specified, then it must follow the final trailing carat. - // CHECKME: this may need to be back-ported to SIMAuthorizeRequest and AIMAuthorizeRequest - // in order to supprot multiple currencies. - - if ($this->getCurrency()) { - $fingerprint .= $this->getCurrency(); - } - - return hash_hmac('md5', $fingerprint, $this->getTransactionKey()); - } - public function getData() { $data = parent::getData(); diff --git a/src/Message/SIMAuthorizeRequest.php b/src/Message/SIMAuthorizeRequest.php index 292c099d..73dab28b 100644 --- a/src/Message/SIMAuthorizeRequest.php +++ b/src/Message/SIMAuthorizeRequest.php @@ -36,6 +36,12 @@ public function getData() return $data; } + /** + * This hash is put into the form to confirm the amount has not been + * modified en-route. + * It uses the TransactionKey, which is a shared secret between the merchant + * and Authorize.Net The sequence and timestamp provide additional salt. + */ public function getHash($data) { $fingerprint = implode( @@ -48,6 +54,11 @@ public function getHash($data) ) ).'^'; + // If x_currency_code is specified, then it must follow the final trailing carat. + if ($this->getCurrency()) { + $fingerprint .= $this->getCurrency(); + } + return hash_hmac('md5', $fingerprint, $this->getTransactionKey()); } From f1b1e3ec013a80bccb1df461794bc0ff1d2791b9 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sun, 15 Feb 2015 13:08:25 +0000 Subject: [PATCH 13/27] Clarify hidden/visible data a little with clearer method names. --- src/Message/DPMAuthorizeResponse.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMAuthorizeResponse.php index 28716995..a492d08e 100644 --- a/src/Message/DPMAuthorizeResponse.php +++ b/src/Message/DPMAuthorizeResponse.php @@ -15,6 +15,9 @@ class DPMAuthorizeResponse extends AbstractResponse implements RedirectResponseI { protected $postUrl; + /** + * These will be hidden fields in the direct-post form. + */ protected $hiddenFields = array( 'x_fp_hash', 'x_amount', @@ -30,6 +33,7 @@ class DPMAuthorizeResponse extends AbstractResponse implements RedirectResponseI 'x_login', 'x_invoice_num', 'x_description', + 'x_cust_id', ); public function __construct(RequestInterface $request, $data, $postUrl) @@ -79,11 +83,11 @@ public function getRedirectData() } /** - * Add a field to the list of hidden fields. + * Move a field to the list of hidden form fields. * The hidden fields are those we don't want to show the user, but * must still be posted. */ - public function setHiddenField($field_name) + public function hideField($field_name) { if (!in_array($field_name, $this->hiddenFields)) { $this->hiddenFields[] = $field_name; @@ -93,7 +97,7 @@ public function setHiddenField($field_name) /** * Remove a field from the list of hidden fields. */ - public function unsetHiddenField($field_name) + public function unhideField($field_name) { if (($key = array_search($field_name, $this->hiddenFields)) !== false) { unset($this->hiddenFields[$key]); @@ -113,7 +117,7 @@ public function getHiddenData() * These are not all mandatory, so you do not have to present all these * to the user. */ - public function getNonHiddenData() + public function getVisibleData() { return array_diff_key($this->getData(), array_flip($this->hiddenFields)); } From 730b7f58fbbea458b68d14495b1c4b967e6945dd Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sun, 15 Feb 2015 16:36:55 +0000 Subject: [PATCH 14/27] Various tidy-up and TODOs comleted. DPM tests still needed. --- src/Message/DPMAuthorizeRequest.php | 31 ++++++++------------- src/Message/DPMAuthorizeResponse.php | 20 ++++++------- src/Message/DPMCompleteRequest.php | 21 ++++++++------ src/Message/DPMCompleteResponse.php | 25 ++++++----------- src/Message/SIMAuthorizeRequest.php | 6 ++++ src/Message/SIMCompleteAuthorizeRequest.php | 3 ++ 6 files changed, 49 insertions(+), 57 deletions(-) diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index 15331049..f4451baf 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -16,34 +16,25 @@ public function getData() { $data = parent::getData(); - // If x_show_form is swet, then the form will be displayed on the Authorize.Net - // gateway, which acts a bit like the SIM gateway. The documentation does NOT - // make this clear. - // TODO: revisit this - maybe much of what is in the DPM can be used to enhance - // the SIM gateway, with very little in the DPM messages. + // 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. - //$data['x_show_form'] = 'PAYMENT_FORM'; unset($data['x_show_form']); - // Support multiple currencies. - // CHECKME: should this be back-ported to SIMAuthorizeRequest and AIMAuthorizeRequest? + // 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->getCurrency()) { - $data['x_currency_code'] = $this->getCurrency(); - } - - // CHECKME: x_recurring_billing is (ambiguously) listed as mandatory in the DPM docs. - - // The customer ID is optional. - if ($this->getCustomerId()) { - $data['x_cust_id'] = $this->getCustomerId(); - } - - // The card details at this point are optional. if ($this->getCard()) { $data['x_card_num'] = $this->getCard()->getNumber(); $data['x_exp_date'] = $this->getCard()->getExpiryDate('my'); $data['x_card_code'] = $this->getCard()->getCvv(); + } else { + $data['x_card_num'] = ''; + $data['x_exp_date'] = ''; + $data['x_card_code'] = ''; } return $data; diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMAuthorizeResponse.php index a492d08e..203d8101 100644 --- a/src/Message/DPMAuthorizeResponse.php +++ b/src/Message/DPMAuthorizeResponse.php @@ -21,6 +21,7 @@ class DPMAuthorizeResponse extends AbstractResponse implements RedirectResponseI protected $hiddenFields = array( 'x_fp_hash', 'x_amount', + 'x_currency_code', 'x_test_request', 'x_cancel_url', 'x_relay_url', @@ -64,7 +65,7 @@ public function isTransparentRedirect() // Helpers to build the form. /** - * The URL the form will be posted to. + * The URL the form will POST to. */ public function getRedirectUrl() { @@ -76,10 +77,12 @@ public function getRedirectMethod() return "post"; } - // CHECKME: do we still need getHiddenData()? + /** + * Data that must be included as hidden fields. + */ public function getRedirectData() { - return $this->getHiddenData(); + return array_intersect_key($this->getData(), array_flip($this->hiddenFields)); } /** @@ -104,18 +107,11 @@ public function unhideField($field_name) } } - /** - * Data that must be included as hidden fields, if they are available at all. - */ - public function getHiddenData() - { - return array_intersect_key($this->getData(), array_flip($this->hiddenFields)); - } - /** * Data not in the hidden fields list. * These are not all mandatory, so you do not have to present all these - * to the user. + * to the user. You may also have custom fields you want to post, such + * as the merchant transactionId (if not using invoiceId for this purpose). */ public function getVisibleData() { diff --git a/src/Message/DPMCompleteRequest.php b/src/Message/DPMCompleteRequest.php index 836f3740..d9eddc3f 100644 --- a/src/Message/DPMCompleteRequest.php +++ b/src/Message/DPMCompleteRequest.php @@ -11,16 +11,24 @@ 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 a *really* bad idea. We are trying to get the data, + // 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'); } @@ -28,13 +36,12 @@ public function getData() // 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 as a GET parameter on the callback URL) then it will - // be checked here. + // 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 up. Someone may have been playing with the + // The amounts don't match. Someone may have been playing with the // transaction references. throw new InvalidRequestException('Incorrect amount'); @@ -45,10 +52,8 @@ public function getData() /** * This hash confirms the ransaction has come from the Authorize.Net gateway. - * It basically tests the shared hash secret is correct, but mixes in other details - * that will change for each transaction so the hash will be unique for each transaction. - * The hash secret and login ID are known to the merchent site, and the amount and transaction - * reference (x_amount and x_trans_id) are sent by the gatewa. + * 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) { diff --git a/src/Message/DPMCompleteResponse.php b/src/Message/DPMCompleteResponse.php index cb077291..0d21b680 100644 --- a/src/Message/DPMCompleteResponse.php +++ b/src/Message/DPMCompleteResponse.php @@ -9,10 +9,10 @@ * 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 GET - * redirect is performed using JavaScript and meta refresh (belt and braces). + * 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). + * page (so the user can correct the form to try again). */ class DPMCompleteResponse extends SIMCompleteAuthorizeResponse implements RedirectResponseInterface { @@ -35,11 +35,6 @@ public function isError() return isset($this->data['x_response_code']) && static::RESPONSE_CODE_ERROR === $this->data['x_response_code']; } - public function getMessage() - { - return parent::getReasonCode() . '|' . parent::getMessage(); - } - /** * 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 @@ -51,11 +46,10 @@ public function isRedirect() } /** - * We default here to POST because the default redirect mechanism - * in Omnipay Common only generates a HTML snippet for POST and not - * GET. - * TODO: We could fix that here so both GET and POST can be supported. - * Our fix should also include the "form data" with the URL. + * 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() { @@ -66,7 +60,7 @@ public function getRedirectMethod() * 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 in some way so the authorisation result cannot be faked. + * unless it is hashed so the authorisation result cannot be faked. */ public function getRedirectData() { @@ -75,9 +69,6 @@ public function getRedirectData() /** * The cancel URL is never handled here - that is a direct link from the gateway. - * The best approach is to have just one redirect URL, and once there, check the - * result of the authorisation in the database (assuming it has been saved in the - * callback) and take action from there. */ public function getRedirectUrl() { diff --git a/src/Message/SIMAuthorizeRequest.php b/src/Message/SIMAuthorizeRequest.php index cad331f4..073293b8 100644 --- a/src/Message/SIMAuthorizeRequest.php +++ b/src/Message/SIMAuthorizeRequest.php @@ -21,14 +21,20 @@ public function getData() $data['x_delim_data'] = 'FALSE'; $data['x_show_form'] = 'PAYMENT_FORM'; $data['x_relay_response'] = 'TRUE'; + // The returnUrl MUST be set in Authorize.net admin panel as a // "Response/Receipt URLs" URL, but not necessarily the default. $data['x_relay_url'] = $this->getReturnUrl(); $data['x_cancel_url'] = $this->getCancelUrl(); + if ($this->getCustomerId() !== null) { $data['x_cust_id'] = $this->getCustomerId(); } + if ($this->getCurrency() !== null) { + $data['x_currency_code'] = $this->getCurrency(); + } + if ($this->getTestMode()) { $data['x_test_request'] = 'TRUE'; } diff --git a/src/Message/SIMCompleteAuthorizeRequest.php b/src/Message/SIMCompleteAuthorizeRequest.php index e3ecbb4a..274abc1e 100644 --- a/src/Message/SIMCompleteAuthorizeRequest.php +++ b/src/Message/SIMCompleteAuthorizeRequest.php @@ -18,6 +18,9 @@ public function getData() return $this->httpRequest->request->all(); } + /** + * CHECKME: DPM uses the transactionReference in the hash, not the transactionID. + */ public function getHash() { return md5($this->getHashSecret().$this->getApiLoginId().$this->getTransactionId().$this->getAmount()); From 1070058c058b6564e02b555409aee515a3394b96 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sun, 15 Feb 2015 22:19:10 +0000 Subject: [PATCH 15/27] Workaround for https://github.com/thephpleague/omnipay-common/issues/29 --- src/Message/DPMAuthorizeRequest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index f4451baf..eff56260 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -29,7 +29,9 @@ public function getData() if ($this->getCard()) { $data['x_card_num'] = $this->getCard()->getNumber(); - $data['x_exp_date'] = $this->getCard()->getExpiryDate('my'); + // 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'] = ''; From 35a3d583b7d7cc18457b52ce7107a3b44baa1d3a Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sun, 15 Feb 2015 23:58:57 +0000 Subject: [PATCH 16/27] Started adding some tests. --- src/AIMGateway.php | 20 +++++++ src/Message/AbstractRequest.php | 10 ++++ src/Message/DPMAuthorizeRequest.php | 2 + src/Message/DPMAuthorizeResponse.php | 5 ++ src/Message/SIMAuthorizeRequest.php | 4 +- tests/DPMGatewayTest.php | 85 ++++++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/DPMGatewayTest.php diff --git a/src/AIMGateway.php b/src/AIMGateway.php index ded28cc0..d7090963 100644 --- a/src/AIMGateway.php +++ b/src/AIMGateway.php @@ -62,6 +62,26 @@ public function setEndpoints($endpoints) 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); diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index 18d11a7f..66998ec5 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -57,6 +57,11 @@ 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); @@ -67,6 +72,11 @@ public function setDeveloperEndpoint($value) return $this->setParameter('developerEndpoint', $value); } + public function getDeveloperEndpoint() + { + return $this->getParameter('developerEndpoint'); + } + protected function getBaseData() { $data = array(); diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index eff56260..008e4013 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -29,9 +29,11 @@ public function getData() 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'] = ''; diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMAuthorizeResponse.php index 203d8101..0ad21b80 100644 --- a/src/Message/DPMAuthorizeResponse.php +++ b/src/Message/DPMAuthorizeResponse.php @@ -62,6 +62,11 @@ public function isTransparentRedirect() return true; } + public function isRedirect() + { + return true; + } + // Helpers to build the form. /** diff --git a/src/Message/SIMAuthorizeRequest.php b/src/Message/SIMAuthorizeRequest.php index 073293b8..f960e472 100644 --- a/src/Message/SIMAuthorizeRequest.php +++ b/src/Message/SIMAuthorizeRequest.php @@ -22,8 +22,8 @@ public function getData() $data['x_show_form'] = 'PAYMENT_FORM'; $data['x_relay_response'] = 'TRUE'; - // The returnUrl MUST be set in Authorize.net admin panel as a - // "Response/Receipt URLs" URL, but not necessarily the default. + // The returnUrl MUST be set in Authorize.net admin panel under + // "Response/Receipt URLs". $data['x_relay_url'] = $this->getReturnUrl(); $data['x_cancel_url'] = $this->getCancelUrl(); diff --git a/tests/DPMGatewayTest.php b/tests/DPMGatewayTest.php new file mode 100644 index 00000000..c9aa8460 --- /dev/null +++ b/tests/DPMGatewayTest.php @@ -0,0 +1,85 @@ +gateway = new DPMGateway($this->getHttpClient(), $this->getHttpRequest()); + $this->gateway->setApiLoginId('example'); + $this->gateway->setTransactionKey('keykey'); + $this->gateway->setHashSecret('elpmaxe'); + + $this->options = array( + 'amount' => '10.00', + 'transactionId' => '99', + 'returnUrl' => 'https://www.example.com/return', + ); + } + + public function testAuthorize() + { + $response = $this->gateway->authorize($this->options)->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertNotEmpty($response->getRedirectUrl()); + + $redirectData = $response->getRedirectData(); + $this->assertSame('https://www.example.com/return', $redirectData['x_relay_url']); + } + + public function testCompleteAuthorize() + { + $this->getHttpRequest()->request->replace( + array( + 'x_response_code' => '1', + 'x_trans_id' => '12345', + 'x_amount' => '10.00', + 'x_MD5_Hash' => md5('elpmaxe' . 'example' . '12345' . '10.00'), + ) + ); + + $response = $this->gateway->completeAuthorize($this->options)->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertSame('12345', $response->getTransactionReference()); + $this->assertNull($response->getMessage()); + } + + public function testPurchase() + { + $response = $this->gateway->purchase($this->options)->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertNotEmpty($response->getRedirectUrl()); + + $redirectData = $response->getRedirectData(); + $this->assertSame('https://www.example.com/return', $redirectData['x_relay_url']); + } + + public function testCompletePurchase() + { + $this->getHttpRequest()->request->replace( + array( + 'x_response_code' => '1', + 'x_trans_id' => '12345', + 'x_amount' => '10.00', + 'x_MD5_Hash' => md5('elpmaxe' . 'example' . '12345' . '10.00'), + ) + ); + + $response = $this->gateway->completePurchase($this->options)->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertSame('12345', $response->getTransactionReference()); + $this->assertNull($response->getMessage()); + } +} From 221f8df8ce04c91efd7bfb763466f57070120581 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Mon, 16 Feb 2015 00:15:13 +0000 Subject: [PATCH 17/27] Dev snapshot while working on tests (time for bed) --- tests/DPMGatewayTest.php | 28 +++++++++++++++++++++++ tests/Message/SIMAuthorizeRequestTest.php | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/DPMGatewayTest.php b/tests/DPMGatewayTest.php index c9aa8460..ae199225 100644 --- a/tests/DPMGatewayTest.php +++ b/tests/DPMGatewayTest.php @@ -35,6 +35,10 @@ public function testAuthorize() $this->assertSame('https://www.example.com/return', $redirectData['x_relay_url']); } + /** + * The MD4 Hash consists of the shared secret, the login ID, the transaction *reference* (as + * generated on the remote gateway for the transaction) and the amount. + */ public function testCompleteAuthorize() { $this->getHttpRequest()->request->replace( @@ -53,6 +57,30 @@ public function testCompleteAuthorize() $this->assertNull($response->getMessage()); } + /** + * @expectedException Omnipay\Common\Exception\InvalidRequestException + * @expectedExceptionMessage Incorrect amount + * + * The hash is correct, so the sender knows the shared secret, but the amount + * is not what we expected, i.e. what we had requested to be authorized. + */ + public function testCompleteAuthorizeWrongAmount() + { + $this->getHttpRequest()->request->replace( + array( + 'x_response_code' => '1', + 'x_trans_id' => '12345', + 'x_amount' => '20.00', + 'x_MD5_Hash' => md5('elpmaxe' . 'example' . '12345' . '20.00'), + ) + ); + + $response = $this->gateway->completeAuthorize($this->options)->send(); + //$this->assertTrue($response->isSuccessful()); + //$this->assertSame('12345', $response->getTransactionReference()); + //$this->assertNull($response->getMessage()); + } + public function testPurchase() { $response = $this->gateway->purchase($this->options)->send(); diff --git a/tests/Message/SIMAuthorizeRequestTest.php b/tests/Message/SIMAuthorizeRequestTest.php index 559405fd..0e3c6aba 100644 --- a/tests/Message/SIMAuthorizeRequestTest.php +++ b/tests/Message/SIMAuthorizeRequestTest.php @@ -57,7 +57,7 @@ public function testSend() $this->assertFalse($response->isSuccessful()); $this->assertTrue($response->isRedirect()); - $this->assertNotEmpty($response->getRedirectUrl()); + $this->assertNotEmpty($response->getRedirectUrl()); // This test is failing; not sure why. $this->assertSame('POST', $response->getRedirectMethod()); $redirectData = $response->getRedirectData(); From e98c2e3004e7e4fffe75e5352fa169126408b04b Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Tue, 17 Feb 2015 01:17:38 +0000 Subject: [PATCH 18/27] Added x_relay_always as specified in new Authorize.Net API guide. Test needed for this. --- src/Message/DPMAuthorizeRequest.php | 5 +++++ tests/DPMGatewayTest.php | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index 008e4013..8a9a6c5a 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -23,6 +23,11 @@ public function getData() 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. diff --git a/tests/DPMGatewayTest.php b/tests/DPMGatewayTest.php index ae199225..e9b3851d 100644 --- a/tests/DPMGatewayTest.php +++ b/tests/DPMGatewayTest.php @@ -76,9 +76,6 @@ public function testCompleteAuthorizeWrongAmount() ); $response = $this->gateway->completeAuthorize($this->options)->send(); - //$this->assertTrue($response->isSuccessful()); - //$this->assertSame('12345', $response->getTransactionReference()); - //$this->assertNull($response->getMessage()); } public function testPurchase() From a8cb0c9cf7b58017ec7d2493d7bd4aa7cf9e1e56 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Tue, 24 Feb 2015 11:03:56 +0000 Subject: [PATCH 19/27] Support clientIp for advanced AFDS features. --- src/Message/DPMAuthorizeRequest.php | 2 -- src/Message/DPMAuthorizeResponse.php | 1 + src/Message/SIMAuthorizeRequest.php | 4 ++++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index 8a9a6c5a..abef9483 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -2,8 +2,6 @@ namespace Omnipay\AuthorizeNet\Message; -use Omnipay\AuthorizeNet\Message\SIMAbstractRequest; - /** * Authorize.Net DPM Authorize Request. * Takes the data that will be used to create the direct-post form. diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMAuthorizeResponse.php index 0ad21b80..1b3e1f6d 100644 --- a/src/Message/DPMAuthorizeResponse.php +++ b/src/Message/DPMAuthorizeResponse.php @@ -35,6 +35,7 @@ class DPMAuthorizeResponse extends AbstractResponse implements RedirectResponseI 'x_invoice_num', 'x_description', 'x_cust_id', + 'x_customer_ip', ); public function __construct(RequestInterface $request, $data, $postUrl) diff --git a/src/Message/SIMAuthorizeRequest.php b/src/Message/SIMAuthorizeRequest.php index f960e472..7debc89f 100644 --- a/src/Message/SIMAuthorizeRequest.php +++ b/src/Message/SIMAuthorizeRequest.php @@ -22,6 +22,10 @@ public function getData() $data['x_show_form'] = 'PAYMENT_FORM'; $data['x_relay_response'] = 'TRUE'; + if ($this->getClientIp()) { + $data['x_customer_ip'] = $this->getClientIp(); + } + // The returnUrl MUST be set in Authorize.net admin panel under // "Response/Receipt URLs". $data['x_relay_url'] = $this->getReturnUrl(); From 6010b7abc59475075f6b47ab631a9a2626970d95 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Tue, 24 Feb 2015 21:09:00 +0000 Subject: [PATCH 20/27] Moved DPMAuthorizeResponse to DPMResponse as it serves as DPMPurchaseResponse too --- src/Message/DPMAuthorizeRequest.php | 2 +- src/Message/{DPMAuthorizeResponse.php => DPMResponse.php} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/Message/{DPMAuthorizeResponse.php => DPMResponse.php} (93%) diff --git a/src/Message/DPMAuthorizeRequest.php b/src/Message/DPMAuthorizeRequest.php index abef9483..62425e04 100644 --- a/src/Message/DPMAuthorizeRequest.php +++ b/src/Message/DPMAuthorizeRequest.php @@ -56,6 +56,6 @@ public function getData() */ public function sendData($data) { - return $this->response = new DPMAuthorizeResponse($this, $data, $this->getEndpoint()); + return $this->response = new DPMResponse($this, $data, $this->getEndpoint()); } } diff --git a/src/Message/DPMAuthorizeResponse.php b/src/Message/DPMResponse.php similarity index 93% rename from src/Message/DPMAuthorizeResponse.php rename to src/Message/DPMResponse.php index 1b3e1f6d..7ceeb7fc 100644 --- a/src/Message/DPMAuthorizeResponse.php +++ b/src/Message/DPMResponse.php @@ -7,11 +7,11 @@ use Omnipay\Common\Message\RedirectResponseInterface; /** - * Authorize.Net DPM Authorize Response - * Here we want the application to present a POST form to the user. This object will + * Authorize.Net DPM Authorize and Purchase Response + * We want the application to present a POST form to the user. This object will * provide the helper methods for doing so. */ -class DPMAuthorizeResponse extends AbstractResponse implements RedirectResponseInterface +class DPMResponse extends AbstractResponse implements RedirectResponseInterface { protected $postUrl; From cf12bd690480cc8706a5470a6de92e2ab368dd08 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Tue, 24 Feb 2015 23:32:35 +0000 Subject: [PATCH 21/27] Additional fields moved to hidden field list for DPM. --- src/Message/AbstractRequest.php | 3 ++ src/Message/DPMResponse.php | 49 +++++++++++++++++++++++------ src/Message/SIMAuthorizeRequest.php | 2 ++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index 66998ec5..23b2717e 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -77,6 +77,9 @@ public function getDeveloperEndpoint() return $this->getParameter('developerEndpoint'); } + /** + * Base data used only for the AIM API. + */ protected function getBaseData() { $data = array(); diff --git a/src/Message/DPMResponse.php b/src/Message/DPMResponse.php index 7ceeb7fc..d5982ae1 100644 --- a/src/Message/DPMResponse.php +++ b/src/Message/DPMResponse.php @@ -17,25 +17,54 @@ class DPMResponse extends AbstractResponse implements RedirectResponseInterface /** * These will be hidden fields in the direct-post form. + * Not all are required */ protected $hiddenFields = array( + // Merchant + 'x_login', + + // Fingerprint 'x_fp_hash', + 'x_fp_sequence', + 'x_fp_timestamp', + + // Transaction + 'x_type', + 'x_version', + 'x_method', + + // Payment 'x_amount', 'x_currency_code', - 'x_test_request', - 'x_cancel_url', - 'x_relay_url', + 'x_tax', + 'x_freight', + 'x_duty', + 'x_tax_exempt', + + // Relay response 'x_relay_response', - 'x_show_form', - 'x_delim_data', - 'x_fp_timestamp', - 'x_fp_sequence', - 'x_type', - 'x_login', + 'x_relay_url', + 'x_relay_always', + 'x_cancel_url', + + // AFDS + 'x_customer_ip', + + // Testing + 'x_test_request', + 'x_invoice_num', 'x_description', 'x_cust_id', - 'x_customer_ip', + 'x_email_customer', + + 'x_delim_data', + ); + + /** + * Maps OmniPay field names to Authorize.Net field names. + */ + protected $fieldMapping = array( ); public function __construct(RequestInterface $request, $data, $postUrl) diff --git a/src/Message/SIMAuthorizeRequest.php b/src/Message/SIMAuthorizeRequest.php index 7debc89f..b0598e24 100644 --- a/src/Message/SIMAuthorizeRequest.php +++ b/src/Message/SIMAuthorizeRequest.php @@ -16,6 +16,8 @@ public function getData() $data = array(); $data['x_login'] = $this->getApiLoginId(); $data['x_type'] = $this->action; + $data['x_version'] = '3.1'; + $data['x_method'] = 'CC'; $data['x_fp_sequence'] = mt_rand(); $data['x_fp_timestamp'] = time(); $data['x_delim_data'] = 'FALSE'; From 4e49d8c87c1aa4440e6a9aabb6d2bb8120ee5073 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Wed, 25 Feb 2015 00:02:31 +0000 Subject: [PATCH 22/27] Created some test coverage for DPM. --- src/AIMGateway.php | 2 +- src/Message/DPMResponse.php | 2 +- tests/Message/DPMAuthorizeRequestTest.php | 68 +++++++++++++++++++++++ tests/Message/DPMPurchaseRequestTest.php | 34 ++++++++++++ tests/Message/SIMAuthorizeRequestTest.php | 4 +- 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 tests/Message/DPMAuthorizeRequestTest.php create mode 100644 tests/Message/DPMPurchaseRequestTest.php diff --git a/src/AIMGateway.php b/src/AIMGateway.php index d7090963..c4c354f7 100644 --- a/src/AIMGateway.php +++ b/src/AIMGateway.php @@ -22,7 +22,7 @@ public function getDefaultParameters() 'testMode' => false, 'developerMode' => false, 'liveEndpoint' => 'https://secure.authorize.net/gateway/transact.dll', - 'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll' + 'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll', ); } diff --git a/src/Message/DPMResponse.php b/src/Message/DPMResponse.php index d5982ae1..2a1f59c9 100644 --- a/src/Message/DPMResponse.php +++ b/src/Message/DPMResponse.php @@ -109,7 +109,7 @@ public function getRedirectUrl() public function getRedirectMethod() { - return "post"; + return 'POST'; } /** diff --git a/tests/Message/DPMAuthorizeRequestTest.php b/tests/Message/DPMAuthorizeRequestTest.php new file mode 100644 index 00000000..5f8b96b6 --- /dev/null +++ b/tests/Message/DPMAuthorizeRequestTest.php @@ -0,0 +1,68 @@ +request = new DPMAuthorizeRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->initialize( + array( + 'clientIp' => '10.0.0.1', + 'amount' => '12.00', + 'returnUrl' => 'https://www.example.com/return', + 'liveEndpoint' => 'https://secure.authorize.net/gateway/transact.dll', + 'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll', + ) + ); + } + + public function testGetData() + { + $data = $this->request->getData(); + + $this->assertSame('AUTH_ONLY', $data['x_type']); + $this->assertArrayNotHasKey('x_show_form', $data); + $this->assertArrayNotHasKey('x_test_request', $data); + } + + public function testGetDataTestMode() + { + $this->request->setTestMode(true); + + $data = $this->request->getData(); + + $this->assertSame('TRUE', $data['x_test_request']); + } + + public function testGetHash() + { + $this->request->setApiLoginId('user'); + $this->request->setTransactionKey('key'); + $data = array( + 'x_fp_sequence' => 'a', + 'x_fp_timestamp' => 'b', + 'x_amount' => 'c', + ); + + $expected = hash_hmac('md5', 'user^a^b^c^', 'key'); + + $this->assertSame($expected, $this->request->getHash($data)); + } + + public function testSend() + { + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertNotEmpty($response->getRedirectUrl()); + $this->assertSame('POST', $response->getRedirectMethod()); + + $redirectData = $response->getRedirectData(); + $this->assertSame('https://www.example.com/return', $redirectData['x_relay_url']); + } +} diff --git a/tests/Message/DPMPurchaseRequestTest.php b/tests/Message/DPMPurchaseRequestTest.php new file mode 100644 index 00000000..d0202dd5 --- /dev/null +++ b/tests/Message/DPMPurchaseRequestTest.php @@ -0,0 +1,34 @@ +request = new DPMPurchaseRequest($this->getHttpClient(), $this->getHttpRequest()); + $this->request->initialize( + array( + 'clientIp' => '10.0.0.1', + 'amount' => '12.00', + 'customerId' => 'cust-id', + 'card' => $this->getValidCard(), + 'returnUrl' => 'https://www.example.com/return', + 'liveEndpoint' => 'https://secure.authorize.net/gateway/transact.dll', + 'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll', + ) + ); + } + + public function testGetData() + { + $data = $this->request->getData(); + + $this->assertSame('AUTH_CAPTURE', $data['x_type']); + $this->assertSame('10.0.0.1', $data['x_customer_ip']); + $this->assertSame('cust-id', $data['x_cust_id']); + $this->assertArrayNotHasKey('x_test_request', $data); + } +} diff --git a/tests/Message/SIMAuthorizeRequestTest.php b/tests/Message/SIMAuthorizeRequestTest.php index 0e3c6aba..46c5ca17 100644 --- a/tests/Message/SIMAuthorizeRequestTest.php +++ b/tests/Message/SIMAuthorizeRequestTest.php @@ -14,6 +14,8 @@ public function setUp() 'clientIp' => '10.0.0.1', 'amount' => '12.00', 'returnUrl' => 'https://www.example.com/return', + 'liveEndpoint' => 'https://secure.authorize.net/gateway/transact.dll', + 'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll', ) ); } @@ -57,7 +59,7 @@ public function testSend() $this->assertFalse($response->isSuccessful()); $this->assertTrue($response->isRedirect()); - $this->assertNotEmpty($response->getRedirectUrl()); // This test is failing; not sure why. + $this->assertNotEmpty($response->getRedirectUrl()); $this->assertSame('POST', $response->getRedirectMethod()); $redirectData = $response->getRedirectData(); From 801a674ef27d065e03ccdbdbf8048fdcd1784c8d Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Fri, 6 Mar 2015 22:34:18 +0000 Subject: [PATCH 23/27] Additional DPM tests, including check that CC expiry is empty. --- tests/Message/DPMCompleteRequestTest.php | 62 ++++++++++++++++++++++++ tests/Message/DPMPurchaseRequestTest.php | 56 ++++++++++++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 tests/Message/DPMCompleteRequestTest.php diff --git a/tests/Message/DPMCompleteRequestTest.php b/tests/Message/DPMCompleteRequestTest.php new file mode 100644 index 00000000..96ad814f --- /dev/null +++ b/tests/Message/DPMCompleteRequestTest.php @@ -0,0 +1,62 @@ +request = new DPMCompleteRequest($this->getHttpClient(), $this->getHttpRequest()); + } + + /** + * @expectedException \Omnipay\Common\Exception\InvalidRequestException + * @expectedExceptionMessage Incorrect hash + */ + public function testGetDataInvalid() + { + $this->getHttpRequest()->request->replace(array('x_MD5_Hash' => 'invalid')); + $this->request->getData(); + } + + public function testGetHash() + { + $this->assertSame(md5(''), $this->request->getHash()); + + $this->request->setHashSecret('hashsec'); + $this->request->setApiLoginId('apilogin'); + $this->request->setTransactionId('trnid'); + $this->request->setAmount('10.00'); + + $this->assertSame(md5('hashsecapilogintrnid10.00'), $this->request->getHash()); + } + + public function testSend() + { + // Note: the hash contains no data supplied by the merchant site, apart + // from the secret. This is the first point at which we see the transaction + // reference (x_trans_id), and this hash is to validate that the reference and + // the amount have not be tampered with en-route. + + $this->getHttpRequest()->request->replace( + array( + 'x_response_code' => '1', + 'x_trans_id' => '12345', + 'x_amount' => '10.00', + 'x_MD5_Hash' => strtolower(md5('shhh' . 'user' . '12345' . '10.00')), + ) + ); + $this->request->setApiLoginId('user'); + $this->request->setHashSecret('shhh'); + //$this->request->setAmount('10.00'); + //$this->request->setTransactionReference('12345'); + + $response = $this->request->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertSame('12345', $response->getTransactionReference()); + $this->assertNull($response->getMessage()); + } +} \ No newline at end of file diff --git a/tests/Message/DPMPurchaseRequestTest.php b/tests/Message/DPMPurchaseRequestTest.php index d0202dd5..cceaf94f 100644 --- a/tests/Message/DPMPurchaseRequestTest.php +++ b/tests/Message/DPMPurchaseRequestTest.php @@ -8,13 +8,25 @@ class DPMPurchaseRequestTest extends TestCase { public function setUp() { + // The card for DPM will always start out blank, so remove the card details. + + $validCard = array_merge( + $this->getValidCard(), + array( + 'number' => '', + 'expiryMonth' => '', + 'expiryYear' => '', + 'cvv' => '', + ) + ); + $this->request = new DPMPurchaseRequest($this->getHttpClient(), $this->getHttpRequest()); $this->request->initialize( array( 'clientIp' => '10.0.0.1', 'amount' => '12.00', 'customerId' => 'cust-id', - 'card' => $this->getValidCard(), + 'card' => $validCard, 'returnUrl' => 'https://www.example.com/return', 'liveEndpoint' => 'https://secure.authorize.net/gateway/transact.dll', 'developerEndpoint' => 'https://test.authorize.net/gateway/transact.dll', @@ -29,6 +41,48 @@ public function testGetData() $this->assertSame('AUTH_CAPTURE', $data['x_type']); $this->assertSame('10.0.0.1', $data['x_customer_ip']); $this->assertSame('cust-id', $data['x_cust_id']); + + $this->assertSame('', $data['x_card_num']); + $this->assertSame('', $data['x_exp_date']); + $this->assertSame('', $data['x_card_code']); + $this->assertArrayNotHasKey('x_test_request', $data); } + + public function testGetDataTestMode() + { + $this->request->setTestMode(true); + + $data = $this->request->getData(); + + $this->assertSame('TRUE', $data['x_test_request']); + } + + public function testGetHash() + { + $this->request->setApiLoginId('user'); + $this->request->setTransactionKey('key'); + $data = array( + 'x_fp_sequence' => 'a', + 'x_fp_timestamp' => 'b', + 'x_amount' => 'c', + ); + + $expected = hash_hmac('md5', 'user^a^b^c^', 'key'); + + $this->assertSame($expected, $this->request->getHash($data)); + } + + public function testSend() + { + $response = $this->request->send(); + + $this->assertFalse($response->isSuccessful()); + $this->assertTrue($response->isRedirect()); + $this->assertNotEmpty($response->getRedirectUrl()); + $this->assertSame('POST', $response->getRedirectMethod()); + + $redirectData = $response->getRedirectData(); + $this->assertSame('https://www.example.com/return', $redirectData['x_relay_url']); + } } From f296cfc3c2fc18c16522c18fe80131fef057da18 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sat, 7 Mar 2015 12:18:36 +0000 Subject: [PATCH 24/27] More test coverage. --- tests/Message/DPMCompleteRequestTest.php | 42 +++++++++++++++++++---- tests/Message/DPMCompleteResponseTest.php | 26 ++++++++++++++ tests/Mock/DPMAuthorizeFailure.txt | 10 ++++++ tests/Mock/DPMAuthorizeSuccess.txt | 10 ++++++ 4 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 tests/Message/DPMCompleteResponseTest.php create mode 100644 tests/Mock/DPMAuthorizeFailure.txt create mode 100644 tests/Mock/DPMAuthorizeSuccess.txt diff --git a/tests/Message/DPMCompleteRequestTest.php b/tests/Message/DPMCompleteRequestTest.php index 96ad814f..77732220 100644 --- a/tests/Message/DPMCompleteRequestTest.php +++ b/tests/Message/DPMCompleteRequestTest.php @@ -2,6 +2,10 @@ namespace Omnipay\AuthorizeNet\Message; +/** + * The CompleteRequest object is invoked in the callback handler. + */ + use Omnipay\Tests\TestCase; class DPMCompleteAuthorizeRequestTest extends TestCase @@ -21,21 +25,19 @@ public function testGetDataInvalid() $this->request->getData(); } - public function testGetHash() + public function testGetDpmHash() { $this->assertSame(md5(''), $this->request->getHash()); $this->request->setHashSecret('hashsec'); $this->request->setApiLoginId('apilogin'); - $this->request->setTransactionId('trnid'); - $this->request->setAmount('10.00'); - $this->assertSame(md5('hashsecapilogintrnid10.00'), $this->request->getHash()); + $this->assertSame(md5('hashsec' . 'apilogin' . 'trnid' . '10.00'), $this->request->getDpmHash('trnid', '10.00')); } public function testSend() { - // Note: the hash contains no data supplied by the merchant site, apart + // The hash contains no data supplied by the merchant site, apart // from the secret. This is the first point at which we see the transaction // reference (x_trans_id), and this hash is to validate that the reference and // the amount have not be tampered with en-route. @@ -50,8 +52,8 @@ public function testSend() ); $this->request->setApiLoginId('user'); $this->request->setHashSecret('shhh'); - //$this->request->setAmount('10.00'); - //$this->request->setTransactionReference('12345'); + + $this->request->setAmount('10.00'); $response = $this->request->send(); @@ -59,4 +61,30 @@ public function testSend() $this->assertSame('12345', $response->getTransactionReference()); $this->assertNull($response->getMessage()); } + + /** + * @expectedException \Omnipay\Common\Exception\InvalidRequestException + * @expectedExceptionMessage Incorrect amount + */ + public function testSendWrongAmount() + { + $this->getHttpRequest()->request->replace( + array( + 'x_response_code' => '1', + 'x_trans_id' => '12345', + 'x_amount' => '10.00', + 'x_MD5_Hash' => strtolower(md5('shhh' . 'user' . '12345' . '10.00')), + ) + ); + $this->request->setApiLoginId('user'); + $this->request->setHashSecret('shhh'); + + // In the callback, the merchant application sets the amount that + // was expected to be authorised. We expected 20.00 but are being + // told it was 10.00. + + $this->request->setAmount('20.00'); + + $response = $this->request->send(); + } } \ No newline at end of file diff --git a/tests/Message/DPMCompleteResponseTest.php b/tests/Message/DPMCompleteResponseTest.php new file mode 100644 index 00000000..c4ed11f1 --- /dev/null +++ b/tests/Message/DPMCompleteResponseTest.php @@ -0,0 +1,26 @@ +getMockRequest(), array('x_response_code' => '1', 'x_trans_id' => '12345')); + + $this->assertTrue($response->isSuccessful()); + $this->assertSame('12345', $response->getTransactionReference()); + $this->assertNull($response->getMessage()); + } + + public function testFailure() + { + $response = new DPMCompleteResponse($this->getMockRequest(), array('x_response_code' => '0', 'x_response_reason_text' => 'Declined')); + + $this->assertFalse($response->isSuccessful()); + $this->assertNull($response->getTransactionReference()); + $this->assertSame('Declined', $response->getMessage()); + } +} diff --git a/tests/Mock/DPMAuthorizeFailure.txt b/tests/Mock/DPMAuthorizeFailure.txt new file mode 100644 index 00000000..ac21934f --- /dev/null +++ b/tests/Mock/DPMAuthorizeFailure.txt @@ -0,0 +1,10 @@ +HTTP/1.1 200 OK +CACHE-CONTROL: no-cache +CONNECTION: close +PRAGMA: no-cache +CONTENT-TYPE: application/x-www-form-urlencoded +ACCEPT: */* +CONTENT-LENGTH: 739 +HOST: example.com + +x_response_code=3&x_response_reason_code=98&x_response_reason_text=%28TESTMODE%29+This+transaction+cannot+be+accepted%2E&x_avs_code=P&x_auth_code=000000&x_trans_id=0&x_method=CC&x_card_type=&x_account_number=&x_first_name=&x_last_name=&x_company=&x_address=&x_city=&x_state=&x_zip=&x_country=&x_phone=&x_fax=&x_email=&x_invoice_num=&x_description=&x_type=auth%5Fonly&x_cust_id=&x_ship_to_first_name=&x_ship_to_last_name=&x_ship_to_company=&x_ship_to_address=&x_ship_to_city=&x_ship_to_state=&x_ship_to_zip=&x_ship_to_country=&x_amount=10%2E00&x_tax=0%2E00&x_duty=0%2E00&x_freight=0%2E00&x_tax_exempt=FALSE&x_po_num=&x_MD5_Hash=480AA04BE6E4BF0469B63D9E42BD568A&x_cvv2_resp_code=&x_cavv_response=&x_test_request=true diff --git a/tests/Mock/DPMAuthorizeSuccess.txt b/tests/Mock/DPMAuthorizeSuccess.txt new file mode 100644 index 00000000..81446cc1 --- /dev/null +++ b/tests/Mock/DPMAuthorizeSuccess.txt @@ -0,0 +1,10 @@ +HTTP/1.1 200 OK +CACHE-CONTROL: no-cache +CONNECTION: close +PRAGMA: no-cache +CONTENT-TYPE: application/x-www-form-urlencoded +ACCEPT: */* +CONTENT-LENGTH: 877 +HOST: example.com + +x_response_code=1&x_response_reason_code=1&x_response_reason_text=%28TESTMODE%29+This+transaction+has+been+approved%2E&x_avs_code=P&x_auth_code=000000&x_trans_id=2184493132&x_method=CC&x_card_type=Visa&x_account_number=XXXX4242&x_first_name=Joe&x_last_name=Bloggs&x_company=&x_address=88+Address+2&x_city=City&x_state=State&x_zip=412&x_country=GB&x_phone=01234+567+890&x_fax=&x_email=jason%40academe%2Eco%2Euk&x_invoice_num=975077375&x_description=&x_type=auth%5Fonly&x_cust_id=CUST123&x_ship_to_first_name=Joe&x_ship_to_last_name=Bloggs&x_ship_to_company=&x_ship_to_address=99+Address+2S&x_ship_to_city=CityS&x_ship_to_state=StateS&x_ship_to_zip=412S&x_ship_to_country=GB&x_amount=10%2E00&x_tax=0%2E00&x_duty=0%2E00&x_freight=0%2E00&x_tax_exempt=FALSE&x_po_num=&x_MD5_Hash=480AA04BE6E4BF0469B63D9E42BD568A&x_cvv2_resp_code=&x_cavv_response=&x_test_request=true From 6661d9b179a6c86ab512937918ee1bffb06c8727 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sat, 7 Mar 2015 12:30:16 +0000 Subject: [PATCH 25/27] Some notes. Will raise a ticket against this. --- src/Message/SIMCompleteAuthorizeRequest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Message/SIMCompleteAuthorizeRequest.php b/src/Message/SIMCompleteAuthorizeRequest.php index 274abc1e..585e268b 100644 --- a/src/Message/SIMCompleteAuthorizeRequest.php +++ b/src/Message/SIMCompleteAuthorizeRequest.php @@ -19,7 +19,9 @@ public function getData() } /** - * CHECKME: DPM uses the transactionReference in the hash, not the transactionID. + * CHECKME: should this be the transactionReference in the hash, not the transactionId? + * 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() { From 1f61b24f3c1700a72805c4d310e8ce300ce13a91 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sat, 7 Mar 2015 13:07:37 +0000 Subject: [PATCH 26/27] Wrap some lines to shorten them. --- src/Message/DPMCompleteResponse.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Message/DPMCompleteResponse.php b/src/Message/DPMCompleteResponse.php index 0d21b680..5ee71f05 100644 --- a/src/Message/DPMCompleteResponse.php +++ b/src/Message/DPMCompleteResponse.php @@ -23,7 +23,8 @@ class DPMCompleteResponse extends SIMCompleteAuthorizeResponse implements Redire public function isSuccessful() { - return isset($this->data['x_response_code']) && static::RESPONSE_CODE_APPROVED === $this->data['x_response_code']; + return isset($this->data['x_response_code']) + && static::RESPONSE_CODE_APPROVED === $this->data['x_response_code']; } /** @@ -32,7 +33,8 @@ public function isSuccessful() */ public function isError() { - return isset($this->data['x_response_code']) && static::RESPONSE_CODE_ERROR === $this->data['x_response_code']; + return isset($this->data['x_response_code']) + && static::RESPONSE_CODE_ERROR === $this->data['x_response_code']; } /** From f83008ef2bae5506225028e868f5848af64ea327 Mon Sep 17 00:00:00 2001 From: Jason Judge Date: Sat, 7 Mar 2015 13:09:44 +0000 Subject: [PATCH 27/27] Shorten some more lines. --- src/Message/AbstractRequest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index 23b2717e..3b85dd55 100644 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -144,6 +144,10 @@ public function sendData($data) public function getEndpoint() { - return $this->getDeveloperMode() ? $this->getParameter('developerEndpoint') : $this->getParameter('liveEndpoint'); + if ($this->getDeveloperMode()) { + return $this->getParameter('developerEndpoint'); + } else { + return $this->getParameter('liveEndpoint'); + } } }