From 7aaa395b1dbb8d0277a3c4a228429bf506e734bb Mon Sep 17 00:00:00 2001 From: Mathieu Duffeler Date: Tue, 27 Aug 2013 14:36:03 +0200 Subject: [PATCH] PaymentRequest + tests --- .gitignore | 2 + VERSION | 1 + autoload.php | 10 + composer.json | 6 + lib/Sips/Passphrase.php | 27 +++ lib/Sips/PaymentRequest.php | 171 ++++++++++++++++++ .../ShaComposer/AllParametersShaComposer.php | 31 ++++ lib/Sips/ShaComposer/ShaComposer.php | 15 ++ phpunit.xml.dist | 38 ++++ tests/Sips/Tests/PaymentRequestTest.php | 84 +++++++++ .../AllParametersShaComposerTest.php | 50 +++++ .../Tests/ShaComposer/FakeShaComposer.php | 18 ++ tests/TestCase.php | 40 ++++ tests/bootstrap.php | 6 + 14 files changed, 499 insertions(+) create mode 100644 VERSION create mode 100644 autoload.php create mode 100644 composer.json create mode 100644 lib/Sips/Passphrase.php create mode 100644 lib/Sips/PaymentRequest.php create mode 100644 lib/Sips/ShaComposer/AllParametersShaComposer.php create mode 100644 lib/Sips/ShaComposer/ShaComposer.php create mode 100644 phpunit.xml.dist create mode 100644 tests/Sips/Tests/PaymentRequestTest.php create mode 100644 tests/Sips/Tests/ShaComposer/AllParametersShaComposerTest.php create mode 100644 tests/Sips/Tests/ShaComposer/FakeShaComposer.php create mode 100644 tests/TestCase.php create mode 100644 tests/bootstrap.php diff --git a/.gitignore b/.gitignore index d2367c2..e763c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ nbproject/* +vendor/* +build/* diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8a9ecc2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/autoload.php b/autoload.php new file mode 100644 index 0000000..cef50fb --- /dev/null +++ b/autoload.php @@ -0,0 +1,10 @@ +setUseIncludePath(true); \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d204842 --- /dev/null +++ b/composer.json @@ -0,0 +1,6 @@ +{ + "name": "marlon-be/marlon-sips", + "autoload": { + "psr-0": { "Sips": "lib/" } + } +} \ No newline at end of file diff --git a/lib/Sips/Passphrase.php b/lib/Sips/Passphrase.php new file mode 100644 index 0000000..0faf70e --- /dev/null +++ b/lib/Sips/Passphrase.php @@ -0,0 +1,27 @@ +passphrase = $passphrase; + } + + /** + * String representation + */ + public function __toString() + { + return (string) $this->passphrase; + } +} \ No newline at end of file diff --git a/lib/Sips/PaymentRequest.php b/lib/Sips/PaymentRequest.php new file mode 100644 index 0000000..00da979 --- /dev/null +++ b/lib/Sips/PaymentRequest.php @@ -0,0 +1,171 @@ + '978', 'USD' => '840', 'CHF' => '756', 'GBP' => '826', + 'CAD' => '124', 'JPY' => '392', 'MXP' => '484', 'TRL' => '792', + 'AUD' => '036', 'NZD' => '554', 'NOK' => '578', 'BRC' => '986', + 'ARP' => '032', 'KHR' => '116', 'TWD' => '901', 'SEK' => '752', + 'DKK' => '208', 'KRW' => '410', 'SGD' => '702' + ); + + public $allowedlanguages = array( + 'nl', 'fr', 'ge', 'en', 'sp', 'it' + ); + + public function __construct(ShaComposer $shaComposer) + { + $this->shaComposer = $shaComposer; + } + + /** @return string */ + public function getShaSign() + { + return $this->shaComposer->compose($this->toArray()); + } + + /** @return string */ + public function getSipsUri() + { + return $this->sipsUri; + } + + public function setSipsUri($sipsUri) + { + $this->validateUri($sipsUri); + $this->sipsUri = $sipsUri; + } + + public function setNormalReturnUrl($uri) + { + $this->validateUri($uri); + $this->parameters['normalReturnUrl'] = $uri; + } + + public function setOrderid($orderid) + { + if(strlen($orderid) > 30) { + throw new \InvalidArgumentException("Orderid cannot be longer than 30 characters"); + } + if(preg_match('/[^a-zA-Z0-9_-]/', $orderid)) { + throw new \InvalidArgumentException("Orderid cannot contain special characters"); + } + $this->parameters['orderid'] = $orderid; + } + + /** + * Set amount in cents, eg EUR 12.34 is written as 1234 + */ + public function setAmount($amount) + { + if(!is_int($amount)) { + throw new InvalidArgumentException("Integer expected. Amount is always in cents"); + } + if($amount <= 0) { + throw new InvalidArgumentException("Amount must be a positive number"); + } + if($amount >= 1.0E+15) { + throw new InvalidArgumentException("Amount is too high"); + } + $this->parameters['amount'] = $amount; + + } + + public function setCurrency($currency) + { + if(!array_key_exists(strtoupper($currency), $this->allowedcurrencies)) { + throw new InvalidArgumentException("Unknown currency"); + } + $this->parameters['currencyCode'] = $this->allowedcurrencies[$currency]; + } + + public function setLanguage($language) + { + if(!in_array($language, $this->allowedlanguages)) { + throw new InvalidArgumentException("Invalid language locale"); + } + $this->parameters['customerLanguage'] = $language; + } + + public function __call($method, $args) + { + if(substr($method, 0, 3) == 'set') { + $field = lcfirst(substr($method, 3)); + if(in_array($field, $this->sipsFields)) { + $this->parameters[$field] = $args[0]; + return; + } + } + + if(substr($method, 0, 3) == 'get') { + $field = lcfirst(substr($method, 3)); + if(array_key_exists($field, $this->parameters)) { + return $this->parameters[$field]; + } + } + + throw new BadMethodCallException("Unknown method $method"); + } + + public function toArray() + { + $this->validate(); + return $this->parameters; + } + + /** @return PaymentRequest */ + public static function createFromArray(ShaComposer $shaComposer, array $parameters) + { + $instance = new static($shaComposer); + foreach($parameters as $key => $value) + { + $instance->{"set$key"}($value); + } + return $instance; + } + + public function validate() + { + foreach($this->requiredFields as $field) { + if(empty($this->parameters[$field])) { + throw new \RuntimeException($field . " can not be empty"); + } + } + } + + protected function validateUri($uri) + { + if(!filter_var($uri, FILTER_VALIDATE_URL)) { + throw new InvalidArgumentException("Uri is not valid"); + } + if(strlen($uri) > 200) { + throw new InvalidArgumentException("Uri is too long"); + } + } +} \ No newline at end of file diff --git a/lib/Sips/ShaComposer/AllParametersShaComposer.php b/lib/Sips/ShaComposer/AllParametersShaComposer.php new file mode 100644 index 0000000..33a37e8 --- /dev/null +++ b/lib/Sips/ShaComposer/AllParametersShaComposer.php @@ -0,0 +1,31 @@ +passphrase = $passphrase; + } + + public function compose(array $parameters) + { + // compose SHA string + $shaString = ''; + foreach($parameters as $key => $value) { + $shaString .= $key . '=' . $value; + $shaString .= (array_search($key, array_keys($parameters)) != (count($parameters)-1)) ? '|' : $this->passphrase; + } + + return hash('sha256', $shaString); + } +} \ No newline at end of file diff --git a/lib/Sips/ShaComposer/ShaComposer.php b/lib/Sips/ShaComposer/ShaComposer.php new file mode 100644 index 0000000..1d8fb33 --- /dev/null +++ b/lib/Sips/ShaComposer/ShaComposer.php @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + tests/Sips/Tests/ + + + + + + lib + + + \ No newline at end of file diff --git a/tests/Sips/Tests/PaymentRequestTest.php b/tests/Sips/Tests/PaymentRequestTest.php new file mode 100644 index 0000000..56a4473 --- /dev/null +++ b/tests/Sips/Tests/PaymentRequestTest.php @@ -0,0 +1,84 @@ +provideMinimalPaymentRequest(); + $paymentRequest->validate(); + } + + /** @test */ + public function IsValidWhenAllFieldsAreFilledIn() + { + $paymentRequest = $this->provideCompletePaymentRequest(); + $paymentRequest->validate(); + } + + /** + * @test + * @expectedException \RuntimeException + */ + public function IsInvalidWhenFieldsAreMissing() + { + $paymentRequest = new PaymentRequest(new FakeShaComposer); + $paymentRequest->validate(); + } + + /** @test */ + public function UnimportantParamsUseMagicSetters() + { + $paymentRequest = new PaymentRequest(new FakeShaComposer); + $paymentRequest->setTemplateName('Marlon Sips Test'); + $this->assertEquals('Marlon Sips Test', $paymentRequest->getTemplateName()); + } + + /** + * @test + * @dataProvider provideBadParameters + * @expectedException \InvalidArgumentException + */ + public function BadParametersCauseExceptions($method, $value) + { + $paymentRequest = new PaymentRequest(new FakeShaComposer); + $paymentRequest->$method($value); + } + + /** + * @test + * @expectedException \BadMethodCallException + */ + public function UnknownMethodFails() + { + $paymentRequest = new PaymentRequest(new FakeShaComposer); + $paymentRequest->getFoobar(); + } + + public function provideBadParameters() + { + $notAUri = 'http://not a uri'; + + return array( + array('setAmount', 10.50), + array('setAmount', -1), + array('setCurrency', 'Belgische Frank'), + array('setNormalReturnUrl', $notAUri), + array('setLanguage', 'West-Vlaams') + ); + } + + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function CreateFromArrayInvalid() + { + $paymentRequest = PaymentRequest::createFromArray(new FakeShaComposer, array('language'=>'West-Vlaams')); + } +} \ No newline at end of file diff --git a/tests/Sips/Tests/ShaComposer/AllParametersShaComposerTest.php b/tests/Sips/Tests/ShaComposer/AllParametersShaComposerTest.php new file mode 100644 index 0000000..0b07453 --- /dev/null +++ b/tests/Sips/Tests/ShaComposer/AllParametersShaComposerTest.php @@ -0,0 +1,50 @@ +assertEquals($expectedSha, $composer->compose($request)); + } + + public function provideRequest() + { + $passphrase = new Passphrase('002001000000001_KEY1'); + + $expectedSha1 = '65421e720d149e8a79bdc9a29f6c462ed199b78f32e558c6c5fc15f0084e5384'; + $request1 = array( + 'amount' => 1234, + 'currencyCode' => 978, + 'merchantId' => '002001000000001', + 'normalReturnUrl' => 'http://www.normalreturnurl.com', + 'transactionReference' => 'marlon1', + 'keyVersion' => 1 + ); + + $expectedSha2 = '6f3a60affb6002355cf214f7e630fa5faba993ddcb47c2d1d942773e4c63f896'; + $request2 = array( + 'amount' => 9876, + 'currencyCode' => 978, + 'merchantId' => '002001000000001', + 'normalReturnUrl' => 'http://www.normalreturnurl.com', + 'transactionReference' => 'marlon2', + 'keyVersion' => 1, + 'customerLanguage' => 'nl' + ); + + return array( + array($passphrase, $request1, $expectedSha1), + array($passphrase, $request2, $expectedSha2), + ); + } +} \ No newline at end of file diff --git a/tests/Sips/Tests/ShaComposer/FakeShaComposer.php b/tests/Sips/Tests/ShaComposer/FakeShaComposer.php new file mode 100644 index 0000000..5934f49 --- /dev/null +++ b/tests/Sips/Tests/ShaComposer/FakeShaComposer.php @@ -0,0 +1,18 @@ + '002001000000001', + 'normalReturnUrl' => 'http://www.normalreturnurl.com', + 'transactionReference' => '123456', + 'keyVersion' => 1 + )); + + $paymentRequest->setSipsUri(PaymentRequest::TEST); + + // minimal required fields for Sips (together with merchantId, normalReturnUrl, transactionReference, keyVersion) + $paymentRequest->setAmount(100); + $paymentRequest->setCurrency("EUR"); + + return $paymentRequest; + } + + /** @return PaymentRequest */ + protected function provideCompletePaymentRequest() + { + $paymentRequest = $this->provideMinimalPaymentRequest(); + + $paymentRequest->setLanguage('nl'); + $paymentRequest->setTemplateName('Marlon Sips Test'); + + return $paymentRequest; + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..8d72388 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,6 @@ +