From a340f85001c7b8dfb2a84a63f3d6252f56a12b43 Mon Sep 17 00:00:00 2001 From: Bobby Date: Tue, 27 Sep 2016 20:39:52 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 4 + .travis.yml | 23 +++ LICENSE.md | 21 ++ README.md | 23 +++ composer.json | 34 ++++ config/interswitch.php | 62 ++++++ phpunit.xml | 21 ++ src/Exceptions/InvalidArgException.php | 15 ++ src/Facades/Interswitch.php | 18 ++ src/Interswitch.php | 269 +++++++++++++++++++++++++ src/InterswitchServiceProvider.php | 39 ++++ src/TransRef.php | 106 ++++++++++ tests/InterswitchTest.php | 32 +++ 13 files changed, 667 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 composer.json create mode 100644 config/interswitch.php create mode 100644 phpunit.xml create mode 100644 src/Exceptions/InvalidArgException.php create mode 100644 src/Facades/Interswitch.php create mode 100644 src/Interswitch.php create mode 100644 src/InterswitchServiceProvider.php create mode 100644 src/TransRef.php create mode 100644 tests/InterswitchTest.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97ef0a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +vendor +.DS_Store +composer.lock +build diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..7afc054 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: php +php: + - '5.6' + - '7.0' + - hhvm + - nightly + +matrix: + allow_failures: + - php: hhvm + +install: travis_retry composer install --no-interaction --prefer-source + +script: + - mkdir -p build/logs + - php vendor/bin/phpunit -c phpunit.xml + - phpunit --coverage-text --coverage-clover=coverage.clover + - phpunit --coverage-clover build/logs/clover.xml + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover + - travis_retry php vendor/bin/coveralls -v diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..06f8e53 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2016] [Devs.NG] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..65c21bc --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# laravel-interswitch + +[![Build Status](https://travis-ci.org/devsng/laravel-interswitch.svg)](https://travis-ci.org/devsng/laravel-interswitch) +[![Latest Stable Version](https://poser.pugx.org/devsng/laravel-interswitch/v/stable)](https://packagist.org/packages/devsng/laravel-interswitch) +[![Total Downloads](https://poser.pugx.org/devsng/laravel-interswitch/downloads)](https://packagist.org/packages/devsng/laravel-interswitch) +[![Latest Unstable Version](https://poser.pugx.org/devsng/laravel-interswitch/v/unstable)](https://packagist.org/packages/devsng/laravel-interswitch) +[![License](https://poser.pugx.org/devsng/laravel-interswitch/license)](https://packagist.org/packages/devsng/laravel-interswitch) + + +Interswitch WebPay Integration package for Laravel 5. + + +## Installation + +Please proceed to the [community website](https://community.devs.ng/) for instructions on installing this package. + +## License + +The Laravel Interswitch package is an open-source software initiative of Nigerian Developers Community ([Devs.NG](http://devs.ng)) licensed under the [MIT license](http://opensource.org/licenses/MIT). + +## Official Docs + +Official Interswitch Documentations can be found here - https://connect.interswitchng.com/documentation/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ab8cb38 --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "devsng/laravel-interswitch", + "description": "Interswitch WebPay Integration package for Laravel 5", + "keywords": ["DevsNG", "Interswitch", "Laravel", "WebPay"], + "license": "MIT", + "authors": [{ + "name": "Bobby", + "email": "connect@devs.ng" + }], + "minimum-stability": "stable", + "require": { + "php": ">=5.6.4", + "illuminate/support": "5.*", + "guzzlehttp/guzzle": "5.*|6.*" + }, + "require-dev": { + "phpunit/phpunit": "5.*", + "mockery/mockery": "dev-master" + }, + "autoload": { + "psr-4": { + "DevsNG\\Interswitch\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "DevsNG\\Interswitch\\Test\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit" + } + +} diff --git a/config/interswitch.php b/config/interswitch.php new file mode 100644 index 0000000..c328c65 --- /dev/null +++ b/config/interswitch.php @@ -0,0 +1,62 @@ + [http://community.devs.ng] + * + */ + +return [ + + /* + * Product Identifier code provided by Interswitch + * + * If not provided, Test Product ID would be used. + */ + + 'product_id' => env('INTERSWITCH_PRODUCT_ID', 6205), + + /* + * Payment Item ID provided by Interswitch + * + * If not provided, Test Payment ID would be used. + */ + + 'pay_item_id' => env('INTERSWITCH_PAY_ITEM_ID', 101), + + /* + * Mac Key provided by Interswitch + * + * If not provided, Test Mac Key would be used. + */ + + 'mac_key' => env('INTERSWITCH_MAC_KEY', 'D3D1D05AFE42AD50818167EAC73C109168A0F108F32645C8B59E897FA930DA44F9230910DAC9E20641823799A107A02068F7BC0F4CC41D2952E249552255710F'), + + /* + * Default Currency for Transactions. + * + * 566 = Naira. + * + */ + + 'currency' => 566, + + /* + * Set Test Mode to false if you are ready to Go Live + * + */ + + 'test_mode' => env('INTERSWITCH_TEST_MODE', true), + + /* + * Redirect URL for complete transactions on Interswitch. + * + * This can also be provided when calling the form generation + * method. + * + */ + + 'redirect_url' => 'http://homestead.app/callback', + +]; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..f26ec5b --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,21 @@ + + + + + + ./tests/ + + + diff --git a/src/Exceptions/InvalidArgException.php b/src/Exceptions/InvalidArgException.php new file mode 100644 index 0000000..0163554 --- /dev/null +++ b/src/Exceptions/InvalidArgException.php @@ -0,0 +1,15 @@ + + * @copyright 2016 DevsNG + * @license https://opensource.org/licenses/MIT MIT License + * + * @version Release: @package_version@ + * + * @link https://github.com/devsng/laravel-interswitch + * @since Class available since Release 1.0 + */ + +namespace DevsNG\Interswitch; + +use GuzzleHttp\Client; +use Illuminate\Support\Facades\Config; +use DevsNG\Interswitch\Exceptions\InvalidArgException; + +class Interswitch +{ + protected $productid; + + protected $payitemid; + + protected $amount; + + protected $currency; + + protected $redirecturl; + + protected $txnref; + +// protected $testmode; + + protected $testurl = 'https://stageserv.interswitchng.com/test_paydirect/pay'; + + protected $liveurl = 'https://webpay.interswitchng.com/paydirect/pay'; + + protected $testqueryurl = 'https://stageserv.interswitchng.com/test_paydirect/api/v1/gettransaction.json'; + + protected $livequeryurl = 'https://webpay.interswitchng.com/paydirect/api/v1/gettransaction.json'; + + protected $queryurl; + + protected $action; + + protected $mackey; + + protected $hash; + + /** + * Create a new instance. + */ + public function __construct() + { + $this->setProductID(); + $this->setPayItemID(); + $this->setMacKey(); + $this->setCurrency(); + $this->setBaseUrls(); + } + + /** + * Sanitize and generate form values. + * + * @param array $options + * + * @return array + */ + public function raw(array $options = []) + { + $inputs['action'] = $this->action; + $inputs['product_id'] = $this->productid; + $inputs['amount'] = $this->convert(array_get($options, 'amount')); + $inputs['currency'] = $this->currency; + + $inputs['site_redirect_url'] = $this->setRedirect( + array_get($options, 'site_redirect_url', config('interswitch.redirect_url')) + ); + + $inputs['txn_ref'] = $this->setTxnRef( + array_get($options, 'txn_ref', TransRef::getHashedToken(7)) + ); + + $hashValues = $this->txnref. + $this->productid. + $this->payitemid. + $inputs['amount']. + $inputs['site_redirect_url']. + $this->mackey; + + $inputs['hash'] = array_get($options, 'hash', $this->hash($hashValues)); + + $inputs['pay_item_id'] = $this->payitemid; + $inputs['cust_name'] = $this->setCustName(array_get($options, 'cust_name')); + $inputs['cust_id'] = $this->txnref; + + return $inputs; + } + + /** + * Create a new HTML form with sanitized values. + * + * @param array $options + * + * @return HTML + */ + public function form(array $options = []) + { + $array = $this->raw($options); + + $action = array_get($array, 'action'); + + $form = '
'.PHP_EOL; + $array = array_except($array, ['action']); + + foreach ($array as $key => $value) { + $form .= ''.PHP_EOL; + } + + $form .= ''.PHP_EOL; + $form .= '
'.PHP_EOL; + + return $form; + } + + /** + * Process and Output Callback information from Interswitch. + * + * @param int $amount + * + * @return JSON + */ + public function callback() + { + $array = request()->all(); + $query = $this->query($array); + + $json = json_decode($query, true); + + dd($json['ResponseDescription']); + // return json_decode($query, true); + } + + /** + * Make a GET request for Transaction status. + * + * @param array $array + * + * @return array + */ + public function query(array $array) + { + $product_id = array_get($array, 'product_id', $this->productid); + $txnref = $this->getTxnRef(array_get($array, 'txnref')); + $amount = $this->convert(array_get($array, 'amount')); + $hash = array_get($array, 'hash', $this->hash($product_id.$txnref.$this->mackey)); + + $client = new Client([ + 'base_uri' => $this->queryurl, + 'headers' => [ + 'Hash' => $hash, + ], + ]); + $q = "?productid={$product_id}&transactionreference={$txnref}&amount={$amount}"; + + $response = $client->request('GET', $q); + + // $responseCode = $response->getStatusCode(); + + return $response->getBody(); + } + + private function hash(string $str) + { + $hash = hash('sha512', $str); + $this->hash = $hash; + + return $this->hash; + } + + private function setCurrency() + { + $this->currency = config('interswitch.currency'); + } + + private function setRedirect($url) + { + if (is_null($url)) { + throw new InvalidArgException('Missing site_redirect_url.'); + } + + $this->redirecturl = $url; + + return $this->redirecturl; + } + + private function setPayItemID() + { + $this->payitemid = config('interswitch.pay_item_id'); + } + + private function setProductID() + { + $this->productid = config('interswitch.product_id'); + } + + private function setBaseUrls() + { + $testmode = config('interswitch.test_mode'); + + if ($testmode) { + $this->action = $this->testurl; + $this->queryurl = $this->testqueryurl; + } else { + $this->action = $this->liveurl; + $this->queryurl = $this->livequeryurl; + } + } + + private function setTxnRef($txn_ref) + { + if (is_null($txn_ref)) { + throw new InvalidArgException('Missing value for txn_ref'); + } + + $this->txnref = $txn_ref; + + return $this->txnref; + } + + private function setCustName($custName) + { + if (is_null($custName)) { + throw new InvalidArgException('Missing Customer Name'); + } + + return $custName; + } + + private function setMacKey() + { + $this->mackey = config('interswitch.mac_key'); + } + + private function convert($amount) + { + if (is_null($amount)) { + throw new InvalidArgException('Missing value for amount.'); + } + + $amount = intval($amount) * 100; + $this->amount = $amount; + + return $this->amount; + } + + private function getTxnRef($txnref) + { + if (is_null($txnref)) { + throw new InvalidArgException('Missing value for TxnRef'); + } + + return $txnref; + } +} diff --git a/src/InterswitchServiceProvider.php b/src/InterswitchServiceProvider.php new file mode 100644 index 0000000..7b85b00 --- /dev/null +++ b/src/InterswitchServiceProvider.php @@ -0,0 +1,39 @@ +publishes([ + $path => config_path('interswitch.php') + ]); + + } + + /** + * Register the application services. + * + * @return void + */ + public function register() + { + // + $this->app->bind('interswitch', function() { + return new Interswitch; + }); + + } + +} diff --git a/src/TransRef.php b/src/TransRef.php new file mode 100644 index 0000000..15adb2a --- /dev/null +++ b/src/TransRef.php @@ -0,0 +1,106 @@ + + * + * Source - http://stackoverflow.com/a/13733588/179104 + * - https://gist.github.com/raveren/5555297 + * + */ + +namespace DevsNG\Interswitch; + +class TransRef +{ + /** + * Get the pool to use based on the type of prefix hash. + * + * @param string $type + * + * @return string + */ + private static function getPool($type = 'alnum') + { + switch ($type) { + case 'alnum': + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'alpha': + $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + break; + case 'hexdec': + $pool = '0123456789abcdef'; + break; + case 'numeric': + $pool = '0123456789'; + break; + case 'nozero': + $pool = '123456789'; + break; + case 'distinct': + $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ'; + break; + default: + $pool = (string) $type; + break; + } + + return $pool; + } + + /** + * Generate a random secure crypt figure. + * + * @param int $min + * @param int $max + * + * @return int + */ + private static function secureCrypt($min, $max) + { + $range = $max - $min; + + if ($range < 0) { + return $min; // not so random... + } + + $log = log($range, 2); + $bytes = (int) ($log / 8) + 1; // length in bytes + $bits = (int) $log + 1; // length in bits + $filter = (int) (1 << $bits) - 1; // set all lower bits to 1 + do { + $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes))); + $rnd = $rnd & $filter; // discard irrelevant bits + } while ($rnd >= $range); + + return $min + $rnd; + } + + /** + * Finally, generate a hashed token. + * + * @param int $length + * + * @return string + */ + public static function getHashedToken($length = 25) + { + $token = ''; + $max = strlen(static::getPool()); + for ($i = 0; $i < $length; ++$i) { + $token .= static::getPool()[static::secureCrypt(0, $max)]; + } + + return $token; + } +} diff --git a/tests/InterswitchTest.php b/tests/InterswitchTest.php new file mode 100644 index 0000000..0463f98 --- /dev/null +++ b/tests/InterswitchTest.php @@ -0,0 +1,32 @@ +app = m::mock(DevsNG\Interswitch\Interswitch::class); + } + + public function tearDown() + { + m::close(); + } + + /** @test */ + public function canOutputRaw() + { + $array = $this->app->shouldReceive('raw') + ->andReturn(); + + $this->assertEquals('array', gettype(array($array))); + } + +}