Skip to content

Commit 471a4fb

Browse files
committed
Issue #112 additional validation on encypted response from gateway
1 parent 0657983 commit 471a4fb

File tree

3 files changed

+93
-66
lines changed

3 files changed

+93
-66
lines changed

src/Message/Form/AuthorizeRequest.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,14 @@ class AuthorizeRequest extends DirectAuthorizeRequest
6565
];
6666

6767
/**
68-
* Add the Form-specific details to the base data.
68+
* Get the full set of Sage Pay Form data, most of which is encrypted.
69+
* TxType is only PAYMENT, DEFERRED or AUTHENTICATE
70+
*
6971
* @reurn array
7072
*/
7173
public function getData()
7274
{
73-
$this->validate('currency', 'description', 'encryptionKey');
75+
$this->validate('currency', 'description', 'encryptionKey', 'returnUrl');
7476

7577
// The test mode is included to determine the redirect URL.
7678

@@ -85,6 +87,7 @@ public function getData()
8587

8688
/**
8789
* @return array the data required to be encoded into the form crypt field.
90+
* @throws InvalidRequestException if any mandatory fields are missing
8891
*/
8992
public function getCryptData()
9093
{
@@ -97,7 +100,7 @@ public function getCryptData()
97100
$data = $this->getTokenData($data);
98101
}
99102

100-
// Some parameers specific to Sage Pay Form..
103+
// Some [optional] parameters specific to Sage Pay Form..
101104

102105
if ($this->getCustomerName() !== null) {
103106
$data['CustomerName'] = $this->getCustomerName();
@@ -131,8 +134,6 @@ public function getCryptData()
131134
$data['SendEmail'] = $this->getSendEmail();
132135
}
133136

134-
// TxType: only PAYMENT, DEFERRED or AUTHENTICATE
135-
136137
$data['SuccessURL'] = $this->getReturnUrl();
137138
$data['FailureURL'] = $this->getFailureUrl() ?: $this->getReturnUrl();
138139

src/Message/Form/CompleteAuthorizeRequest.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
use Omnipay\SagePay\Message\AbstractRequest;
1010
use Omnipay\SagePay\Message\Response as GenericResponse;
11+
use Omnipay\Common\Exception\InvalidResponseException;
1112

1213
class CompleteAuthorizeRequest extends AbstractRequest
1314
{
@@ -25,22 +26,43 @@ public function getTxType()
2526

2627
/**
2728
* Data will be encrypted as a query parameter.
29+
*
30+
* @return array
31+
* @throws InvalidResponseException if "crypt" is missing or invalid.
2832
*/
2933
public function getData()
3034
{
35+
// The application has the option of passing the query parameter
36+
// in, perhaps using its own middleware, or allowing Omnipay t0
37+
// provide it.
38+
3139
$crypt = $this->getCrypt() ?: $this->httpRequest->query->get('crypt');
3240

33-
// Remove the leading '@' and decrypt.
41+
// Make sure we have a crypt parameter before trying to decrypt it.
42+
43+
if (empty($crypt) || !is_string($crypt) || substr($crypt, 0, 1) !== '@') {
44+
throw new InvalidResponseException('Missing or invalid "crypt" parameter');
45+
}
46+
47+
// Remove the leading '@' and decrypt the remainder into a query string.
48+
// And E_WARNING error will be issued if the crypt parameter data is not
49+
// a hexadecimal string.
50+
51+
$hexString = substr($crypt, 1);
52+
53+
if (! preg_match('/^[0-9a-f]+$/i', $hexString)) {
54+
throw new InvalidResponseException('Invalid "crypt" parameter; not hexadecimal');
55+
}
3456

35-
$query = openssl_decrypt(
36-
hex2bin(substr($crypt, 1)),
57+
$queryString = openssl_decrypt(
58+
hex2bin($hexString),
3759
'aes-128-cbc',
3860
$this->getEncryptionKey(),
3961
OPENSSL_RAW_DATA,
4062
$this->getEncryptionKey()
4163
);
4264

43-
parse_str($query, $data);
65+
parse_str($queryString, $data);
4466

4567
return($data);
4668
}

tests/FormGatewayTest.php

Lines changed: 61 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
class FormGatewayTest extends GatewayTestCase
88
{
9-
protected $error_3082_text = '3082 : The Description value is too long.';
10-
119
public function setUp()
1210
{
1311
parent::setUp();
@@ -180,86 +178,92 @@ public function testCompleteAuthorizeSuccess()
180178
}
181179

182180
/**
181+
* Invalid without any query parameter supplied.
183182
* @expectedException Omnipay\Common\Exception\InvalidResponseException
184183
*/
185-
/*
186184
public function testCompleteAuthorizeInvalid()
187185
{
188186
$response = $this->gateway->completeAuthorize($this->completePurchaseOptions)->send();
189187
}
190188

191-
public function testPurchaseSuccess()
189+
/**
190+
* Invalid without any query parameter supplied.
191+
* @expectedException Omnipay\Common\Exception\InvalidResponseException
192+
*/
193+
public function testCompletePurchaseInvalid1()
192194
{
193-
$this->setMockHttpResponse('ServerPurchaseSuccess.txt');
194-
195-
$response = $this->gateway->purchase($this->purchaseOptions)->send();
196-
197-
$this->assertFalse($response->isSuccessful());
198-
$this->assertTrue($response->isRedirect());
199-
$this->assertSame('{"SecurityKey":"IK776BWNHN","VPSTxId":"{1E7D9C70-DBE2-4726-88EA-D369810D801D}","VendorTxCode":"123"}', $response->getTransactionReference());
200-
$this->assertSame('Server transaction registered successfully.', $response->getMessage());
201-
$this->assertSame('https://test.sagepay.com/Simulator/VSPServerPaymentPage.asp?TransactionID={1E7D9C70-DBE2-4726-88EA-D369810D801D}', $response->getRedirectUrl());
195+
$response = $this->gateway->completePurchase($this->completePurchaseOptions)->send();
202196
}
203197

204-
public function testPurchaseFailure()
198+
/**
199+
* @expectedException Omnipay\Common\Exception\InvalidResponseException
200+
*/
201+
public function testCompletePurchaseInvalid2()
205202
{
206-
$this->setMockHttpResponse('ServerPurchaseFailure.txt');
207-
208-
$response = $this->gateway->purchase($this->purchaseOptions)->send();
209-
210-
$this->assertFalse($response->isSuccessful());
211-
$this->assertFalse($response->isRedirect());
212-
$this->assertNull($response->getTransactionReference());
213-
$this->assertSame($this->error_3082_text, $response->getMessage());
203+
// No leading '@'.
204+
$this->getHttpRequest()->initialize(['crypt' => 'ababab']);
205+
$response = $this->gateway->completePurchase($this->completePurchaseOptions)->send();
214206
}
215207

216-
public function testCompletePurchaseSuccess()
208+
/**
209+
* @expectedException Omnipay\Common\Exception\InvalidResponseException
210+
*/
211+
public function testCompletePurchaseInvalid3()
217212
{
218-
$this->getHttpRequest()->request->replace(
219-
array(
220-
'Status' => 'OK',
221-
'TxAuthNo' => 'b',
222-
'AVSCV2' => 'c',
223-
'AddressResult' => 'd',
224-
'PostCodeResult' => 'e',
225-
'CV2Result' => 'f',
226-
'GiftAid' => 'g',
227-
'3DSecureStatus' => 'h',
228-
'CAVV' => 'i',
229-
'AddressStatus' => 'j',
230-
'PayerStatus' => 'k',
231-
'CardType' => 'l',
232-
'Last4Digits' => 'm',
233-
'VPSSignature' => md5('{F955C22E-F67B-4DA3-8EA3-6DAC68FA59D2}438791OKbexamplecJEUPDN1N7Edefghijklm'),
234-
)
235-
);
236-
213+
// Not hexadecimal.
214+
$this->getHttpRequest()->initialize(['crypt' => '@ababxx']);
237215
$response = $this->gateway->completePurchase($this->completePurchaseOptions)->send();
238-
239-
$this->assertTrue($response->isSuccessful());
240-
$this->assertSame('{"SecurityKey":"JEUPDN1N7E","TxAuthNo":"b","VPSTxId":"{F955C22E-F67B-4DA3-8EA3-6DAC68FA59D2}","VendorTxCode":"123"}', $response->getTransactionReference());
241-
$this->assertNull($response->getMessage());
242216
}
243-
*/
217+
244218
/**
245-
* @expectedException Omnipay\Common\Exception\InvalidResponseException
219+
* A valid crypt response format, but not decyptable, so empty data.
246220
*/
247-
/*
248-
public function testCompletePurchaseInvalid()
221+
public function testCompletePurchaseInvalid4()
249222
{
223+
$this->getHttpRequest()->initialize(['crypt' => '@ababab']);
250224
$response = $this->gateway->completePurchase($this->completePurchaseOptions)->send();
251-
}
252225

253-
// Repeat Authorize
226+
$this->assertSame([], $response->getData());
227+
}
254228

255-
public function testRepeatAuthorizeSuccess()
229+
/**
230+
* This is on return from the gateway with a valid encrypted result.
231+
*/
232+
public function testCompletePurchaseSuccess()
256233
{
257-
$this->setMockHttpResponse('SharedRepeatAuthorize.txt');
234+
// Set the "crypt" query parameter.
258235

259-
$response = $this->gateway->repeatAuthorize($this->repeatOptions)->send();
236+
$this->getHttpRequest()->initialize(['crypt' => '@5548276239c33e937e4d9d847d0a01f4c05f1b71dd5cd32568b6985a6d6834aca672315cf3eec01bb20d34cd1ccd7bdd69a9cd89047f7f875103b46efd8f7b97847eea6b6bab5eb8b61da9130a75fffa1c9152b7d39f77e534ea870281b8e280ea1fdbd49a8f5a7c67d1f512fe7a030e81ae6bd2beed762ad074edcd5d7eb4456a6797911ec78e4d16e7d3ac96b919052a764b7ee4940fd6976346608ad8fed1eb6b0b14d84d802c594b3fd94378a26837df66b328f01cfd144f2e7bc166370bf7a833862173412d2798e8739ee7ef9b0568afab0fc69f66af19864480bf3e74fe2fd2043ec90396e40ab62dc9c1f32dee0e309af7561d2286380ebb497105bde2860d401ccfb4cfcd7047ad32e9408d37f5d0fe9a67bd964d5b138b2546a7d54647467c59384eaa20728cf240c460e36db68afdcf0291135f9d5ff58563f14856fd28534a5478ba2579234b247d0d5862c5742495a2ae18c5ca0d6461d641c5215b07e690280fa3eaf5d392e1d6e2791b181a500964d4bc6c76310e47468ae72edddc3c04d83363514c908624747118']);
237+
238+
$response = $this->gateway->completePurchase($this->completePurchaseOptions)->send();
260239

261240
$this->assertTrue($response->isSuccessful());
262-
$this->assertSame('Successful repeat.', $response->getMessage());
241+
$this->assertFalse($response->isRedirect());
242+
$this->assertSame('{"TxAuthNo":"19052426","VPSTxId":"{B7792365-F7F9-6E20-ACD1-390C5CEBDDAF}","VendorTxCode":"phpne-demo-56260425"}', $response->getTransactionReference());
243+
$this->assertSame('0000 : The Authorisation was Successful.', $response->getMessage());
244+
$this->assertSame('19052426', $response->getTxAuthNo());
245+
246+
$this->assertSame(
247+
[
248+
'VendorTxCode' => 'phpne-demo-56260425',
249+
'VPSTxId' => '{B7792365-F7F9-6E20-ACD1-390C5CEBDDAF}',
250+
'Status' => 'OK',
251+
'StatusDetail' => '0000 : The Authorisation was Successful.',
252+
'TxAuthNo' => '19052426',
253+
'AVSCV2' => 'SECURITY CODE MATCH ONLY',
254+
'AddressResult' => 'NOTMATCHED',
255+
'PostCodeResult' => 'NOTMATCHED',
256+
'CV2Result' => 'MATCHED',
257+
'GiftAid' => '0',
258+
'3DSecureStatus' => 'NOTCHECKED',
259+
'CardType' => 'VISA',
260+
'Last4Digits' => '0006',
261+
'DeclineCode' => '00',
262+
'ExpiryDate' => '1218',
263+
'Amount' => '99.99',
264+
'BankAuthCode' => '999777',
265+
],
266+
$response->getData()
267+
);
263268
}
264-
*/
265269
}

0 commit comments

Comments
 (0)