diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 5c6751f..87e02b3 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":1,"defects":[],"times":{"Unicodeveloper\\Paystack\\Test\\HelpersTest::it_returns_instance_of_paystack":0.213,"Unicodeveloper\\Paystack\\Test\\PaystackTest::testAllCustomersAreReturned":0.089,"Unicodeveloper\\Paystack\\Test\\PaystackTest::testAllTransactionsAreReturned":0.001,"Unicodeveloper\\Paystack\\Test\\PaystackTest::testAllPlansAreReturned":0.001}} \ No newline at end of file +{"version":2,"defects":[],"times":{"Unicodeveloper\\Paystack\\Test\\TransRefTest::testGeneratesTokenOfCorrectLength":0.001,"Unicodeveloper\\Paystack\\Test\\TransRefTest::testGeneratesDifferentTokens":0,"Unicodeveloper\\Paystack\\Test\\TransRefTest::testUsesDefaultLength":0,"Unicodeveloper\\Paystack\\Test\\PaystackTest::testAllCustomersAreReturned":0.001,"Unicodeveloper\\Paystack\\Test\\PaystackTest::testAllTransactionsAreReturned":0,"Unicodeveloper\\Paystack\\Test\\PaystackTest::testAllPlansAreReturned":0,"Unicodeveloper\\Paystack\\Test\\HelpersTest::testReturnsInstanceOfPaystack":0}} \ No newline at end of file diff --git a/README.md b/README.md index f57e23d..5421988 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,20 @@ Expiry Date: any date in the future CVV: 883 ``` +## Compatibility + +| Laravel Version | PHP Version | +|-----------------|------------| +| 6.x | 7.2+ | +| 7.x | 7.2+ | +| 8.x | 7.2+ | +| 9.x | 8.0+ | +| 10.x | 8.0+ | +| 11.x | 8.1+ | +| 12.x | 8.2+ | + +> This package has been tested with Laravel 6 through 12 and PHP 7.2 through 8.4. + ## Todo * Charge Returning Customers diff --git a/composer.json b/composer.json index e67e985..e7be3bf 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,11 @@ "paystack.co", "laravel 6", "laravel 7", - "laravel 8" + "laravel 8", + "laravel 9", + "laravel 10", + "laravel 11", + "laravel 12" ], "license": "MIT", "authors": [ @@ -27,12 +31,12 @@ ], "minimum-stability": "stable", "require": { - "php": "^7.2|^8.0|^8.1", - "illuminate/support": "~6|~7|~8|~9|^10.0|^11.0", + "php": "^7.2|^8.0|^8.1|^8.2|^8.3|^8.4", + "illuminate/support": "~6|~7|~8|~9|^10.0|^11.0|^12.0", "guzzlehttp/guzzle": "~6|~7|~8|~9" }, "require-dev": { - "phpunit/phpunit": "^8.4|^9.0|^10.5", + "phpunit/phpunit": "^8.4|^9.0|^10.5|^12.3", "scrutinizer/ocular": "~1.1", "php-coveralls/php-coveralls": "^2.0", "mockery/mockery": "^1.3" diff --git a/src/Paystack.php b/src/Paystack.php index f4b91dc..6c79c36 100644 --- a/src/Paystack.php +++ b/src/Paystack.php @@ -58,6 +58,13 @@ class Paystack */ protected $authorizationUrl; + /** + * Authorization URL returned from Paystack after initializing a transaction. + * + * @var string + */ + protected string $url; + public function __construct() { $this->setKey(); @@ -68,7 +75,7 @@ public function __construct() /** * Get Base Url from Paystack config file */ - public function setBaseUrl() + public function setBaseUrl(): void { $this->baseUrl = Config::get('paystack.paymentUrl'); } @@ -76,7 +83,7 @@ public function setBaseUrl() /** * Get secret key from Paystack config file */ - public function setKey() + public function setKey(): void { $this->secretKey = Config::get('paystack.secretKey'); } @@ -84,7 +91,7 @@ public function setKey() /** * Set options for making the Client request */ - private function setRequestOptions() + private function setRequestOptions(): void { $authBearer = 'Bearer ' . $this->secretKey; @@ -100,16 +107,13 @@ private function setRequestOptions() ); } - /** - * Initiate a payment request to Paystack * Included the option to pass the payload to this method for situations * when the payload is built on the fly (not passed to the controller from a view) * @return Paystack */ - - public function makePaymentRequest($data = null) + public function makePaymentRequest(?array $data = null): self { if ($data == null) { @@ -188,7 +192,7 @@ public function makePaymentRequest($data = null) * @return Paystack * @throws IsNullException */ - private function setHttpResponse($relativeUrl, $method, $body = []) + private function setHttpResponse(string $relativeUrl, string $method, array $body = []): self { if (is_null($method)) { throw new IsNullException("Empty method not allowed"); @@ -206,12 +210,10 @@ private function setHttpResponse($relativeUrl, $method, $body = []) * Get the authorization url from the callback response * @return Paystack */ - public function getAuthorizationUrl($data = null) + public function getAuthorizationUrl(?array $data = null): self { $this->makePaymentRequest($data); - $this->url = $this->getResponse()['data']['authorization_url']; - return $this; } @@ -221,24 +223,20 @@ public function getAuthorizationUrl($data = null) * and might need to take different actions based on the success or not of the transaction * @return array */ - public function getAuthorizationResponse($data) + public function getAuthorizationResponse(?array $data): array { $this->makePaymentRequest($data); - $this->url = $this->getResponse()['data']['authorization_url']; - return $this->getResponse(); } /** * Hit Paystack Gateway to Verify that the transaction is valid */ - private function verifyTransactionAtGateway($transaction_id = null) + private function verifyTransactionAtGateway(?string $transaction_id = null): void { $transactionRef = $transaction_id ?? request()->query('trxref'); - $relativeUrl = "/transaction/verify/{$transactionRef}"; - $this->response = $this->client->get($this->baseUrl . $relativeUrl, []); } @@ -246,12 +244,10 @@ private function verifyTransactionAtGateway($transaction_id = null) * True or false condition whether the transaction is verified * @return boolean */ - public function isTransactionVerificationValid($transaction_id = null) + public function isTransactionVerificationValid(?string $transaction_id = null): bool { $this->verifyTransactionAtGateway($transaction_id); - $result = $this->getResponse()['message']; - switch ($result) { case self::VS: $validate = true; @@ -283,8 +279,10 @@ public function getPaymentData() /** * Fluent method to redirect to Paystack Payment Page + * + * @return \Illuminate\Http\RedirectResponse */ - public function redirectNow() + public function redirectNow(): \Illuminate\Http\RedirectResponse { return redirect($this->url); } @@ -293,7 +291,7 @@ public function redirectNow() * Get Access code from transaction callback respose * @return string */ - public function getAccessCode() + public function getAccessCode(): string { return $this->getResponse()['data']['access_code']; } @@ -302,7 +300,7 @@ public function getAccessCode() * Generate a Unique Transaction Reference * @return string */ - public function genTranxRef() + public function genTranxRef(): string { return TransRef::getHashedToken(); } @@ -311,7 +309,7 @@ public function genTranxRef() * Get all the customers that have made transactions on your platform * @return array */ - public function getAllCustomers() + public function getAllCustomers(): array { $this->setRequestOptions(); @@ -322,7 +320,7 @@ public function getAllCustomers() * Get all the plans that you have on Paystack * @return array */ - public function getAllPlans() + public function getAllPlans(): array { $this->setRequestOptions(); @@ -333,7 +331,7 @@ public function getAllPlans() * Get all the transactions that have happened overtime * @return array */ - public function getAllTransactions() + public function getAllTransactions(): array { $this->setRequestOptions(); @@ -342,9 +340,10 @@ public function getAllTransactions() /** * Get the whole response from a get operation - * @return array + * + * @return array|null */ - private function getResponse() + private function getResponse(): ?array { return json_decode($this->response->getBody(), true); } @@ -353,7 +352,7 @@ private function getResponse() * Get the data response from a get operation * @return array */ - private function getData() + private function getData(): array { return $this->getResponse()['data']; } @@ -426,7 +425,7 @@ public function createCustomer($data = null) ]; } - + $this->setRequestOptions(); return $this->setHttpResponse('/customer', 'POST', $data)->getResponse(); } @@ -699,10 +698,10 @@ public function updateSubAccount($subaccount_code) return $this->setHttpResponse("/subaccount/{$subaccount_code}", "PUT", array_filter($data))->getResponse(); } - + /** * Get a list of all supported banks and their properties - * @param $country - The country from which to obtain the list of supported banks, $per_page - Specifies how many records to retrieve per page , + * @param $country - The country from which to obtain the list of supported banks, $per_page - Specifies how many records to retrieve per page , * $use_cursor - Flag to enable cursor pagination on the endpoint * @return array */ diff --git a/src/PaystackServiceProvider.php b/src/PaystackServiceProvider.php index ec2cc54..60461a1 100644 --- a/src/PaystackServiceProvider.php +++ b/src/PaystackServiceProvider.php @@ -24,11 +24,11 @@ class PaystackServiceProvider extends ServiceProvider protected $defer = false; /** - * Publishes all the config file this package needs to function - */ - public function boot() + * Publishes all the config file this package needs to function + */ + public function boot(): void { - $config = realpath(__DIR__.'/../resources/config/paystack.php'); + $config = realpath(__DIR__ . '/../resources/config/paystack.php'); $this->publishes([ $config => config_path('paystack.php') @@ -36,22 +36,20 @@ public function boot() } /** - * Register the application services. - */ - public function register() + * Register the application services. + */ + public function register(): void { $this->app->bind('laravel-paystack', function () { - return new Paystack; - }); } /** - * Get the services provided by the provider - * @return array - */ - public function provides() + * Get the services provided by the provider + * @return array + */ + public function provides(): array { return ['laravel-paystack']; } diff --git a/src/Support/helpers.php b/src/Support/helpers.php index c0a0eaf..c189a49 100644 --- a/src/Support/helpers.php +++ b/src/Support/helpers.php @@ -3,7 +3,6 @@ if (! function_exists("paystack")) { function paystack() { - return app()->make('laravel-paystack'); } -} \ No newline at end of file +} diff --git a/src/TransRef.php b/src/TransRef.php index 7a166c3..95d8eac 100644 --- a/src/TransRef.php +++ b/src/TransRef.php @@ -17,10 +17,10 @@ class TransRef { /** * Get the pool to use based on the type of prefix hash - * @param string $type + * @param string $type * @return string */ - private static function getPool($type = 'alnum') + private static function getPool(string $type = 'alnum'): string { switch ($type) { case 'alnum': @@ -42,7 +42,7 @@ private static function getPool($type = 'alnum') $pool = '2345679ACDEFHJKLMNPRSTUVWXYZ'; break; default: - $pool = (string) $type; + $pool = (string)$type; break; } @@ -51,11 +51,12 @@ private static function getPool($type = 'alnum') /** * Generate a random secure crypt figure - * @param integer $min - * @param integer $max + * Generate a random secure integer between $min and $max - 1 + * @param integer $min + * @param integer $max * @return integer */ - private static function secureCrypt($min, $max) + private static function secureCrypt(int $min, int $max): int { $range = $max - $min; @@ -63,10 +64,10 @@ private static function secureCrypt($min, $max) 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 + $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 @@ -77,13 +78,13 @@ private static function secureCrypt($min, $max) /** * Finally, generate a hashed token - * @param integer $length + * @param integer $length * @return string */ - public static function getHashedToken($length = 25) + public static function getHashedToken(int $length = 25): string { $token = ""; - $max = strlen(static::getPool()); + $max = strlen(static::getPool()); for ($i = 0; $i < $length; $i++) { $token .= static::getPool()[static::secureCrypt(0, $max)]; } diff --git a/tests/HelpersTest.php b/tests/HelpersTest.php index 7bb3da9..2ca8e98 100644 --- a/tests/HelpersTest.php +++ b/tests/HelpersTest.php @@ -4,20 +4,24 @@ use Mockery as m; use PHPUnit\Framework\TestCase; +use Unicodeveloper\Paystack\Paystack; -class HelpersTest extends TestCase { - - protected $paystack; +class HelpersTest extends TestCase +{ + protected Paystack|\Mockery\MockInterface $paystack; + protected \Mockery\MockInterface $mock; public function setUp(): void { - $this->paystack = m::mock('Unicodeveloper\Paystack\Paystack'); - $this->mock = m::mock('GuzzleHttp\Client'); + parent::setUp(); + $this->paystack = m::mock(Paystack::class); + $this->mock = m::mock(\GuzzleHttp\Client::class); } public function tearDown(): void { m::close(); + parent::tearDown(); } /** @@ -26,8 +30,8 @@ public function tearDown(): void * @test * @return void */ - function it_returns_instance_of_paystack () { - - $this->assertInstanceOf("Unicodeveloper\Paystack\Paystack", $this->paystack); + function testReturnsInstanceOfPaystack(): void + { + $this->assertInstanceOf(Paystack::class, $this->paystack); } -} \ No newline at end of file +} diff --git a/tests/PaystackTest.php b/tests/PaystackTest.php index cabd082..64d9c17 100644 --- a/tests/PaystackTest.php +++ b/tests/PaystackTest.php @@ -1,56 +1,63 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - namespace Unicodeveloper\Paystack\Test; use Mockery as m; -use GuzzleHttp\Client; use PHPUnit\Framework\TestCase; use Unicodeveloper\Paystack\Paystack; -use Illuminate\Support\Facades\Config; -use Illuminate\Support\Facades\Facade as Facade; class PaystackTest extends TestCase { - protected $paystack; + protected Paystack|\Mockery\MockInterface $paystack; + + /** + * Mocked HTTP client (if needed) + * + * @var \Mockery\MockInterface|\GuzzleHttp\Client + */ + protected \Mockery\MockInterface $mock; - public function setUp(): void + protected function setUp(): void { - $this->paystack = m::mock('Unicodeveloper\Paystack\Paystack'); + parent::setUp(); + + $this->paystack = m::mock(Paystack::class); $this->mock = m::mock('GuzzleHttp\Client'); } - public function tearDown(): void + protected function tearDown(): void { m::close(); + parent::tearDown(); } - public function testAllCustomersAreReturned() + public function testAllCustomersAreReturned(): void { - $array = $this->paystack->shouldReceive('getAllCustomers')->andReturn(['prosper']); + $this->paystack->shouldReceive('getAllCustomers')->andReturn(['prosper']); - $this->assertEquals('array', gettype(array($array))); + $result = $this->paystack->getAllCustomers(); + + $this->assertIsArray($result); + $this->assertContains('prosper', $result); } - public function testAllTransactionsAreReturned() + public function testAllTransactionsAreReturned(): void { - $array = $this->paystack->shouldReceive('getAllTransactions')->andReturn(['transactions']); + $this->paystack->shouldReceive('getAllTransactions')->andReturn(['transactions']); + + $result = $this->paystack->getAllTransactions(); - $this->assertEquals('array', gettype(array($array))); + $this->assertIsArray($result); + $this->assertContains('transactions', $result); } - public function testAllPlansAreReturned() + public function testAllPlansAreReturned(): void { - $array = $this->paystack->shouldReceive('getAllPlans')->andReturn(['intermediate-plan']); + $this->paystack->shouldReceive('getAllPlans')->andReturn(['intermediate-plan']); + + $result = $this->paystack->getAllPlans(); - $this->assertEquals('array', gettype(array($array))); + $this->assertIsArray($result); + $this->assertContains('intermediate-plan', $result); } } diff --git a/tests/TransRefTest.php b/tests/TransRefTest.php new file mode 100644 index 0000000..c91f893 --- /dev/null +++ b/tests/TransRefTest.php @@ -0,0 +1,62 @@ +deprecations[] = $errstr; + } + return false; // Continue with normal error handling + }, E_DEPRECATED); + } + + public function tearDown(): void + { + restore_error_handler(); + + // Output any captured deprecations + if (!empty($this->deprecations)) { + fwrite(STDERR, "Captured deprecations in " . $this->getName() . ":\n"); + foreach ($this->deprecations as $deprecation) { + fwrite(STDERR, " - " . $deprecation . "\n"); + } + } + + parent::tearDown(); + } + + public function testGeneratesTokenOfCorrectLength(): void + { + $token = TransRef::getHashedToken(32); + + $this->assertIsString($token); + $this->assertSame(32, strlen($token)); + } + + public function testGeneratesDifferentTokens(): void + { + $token1 = TransRef::getHashedToken(16); + $token2 = TransRef::getHashedToken(16); + + $this->assertNotSame($token1, $token2, 'Tokens should be random'); + } + + public function testUsesDefaultLength(): void + { + $token = TransRef::getHashedToken(); + + $this->assertSame(25, strlen($token)); + } +} \ No newline at end of file