From 32d12275c228130e15be86c6ee0ca394e63062b7 Mon Sep 17 00:00:00 2001 From: tolu-paystack Date: Wed, 11 Feb 2026 11:40:34 +0100 Subject: [PATCH 1/6] chore: add phpunit cache to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2cd2a50..8400912 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ vendor nbproject .env .idea/ -tests/EndToEndTests.php \ No newline at end of file +tests/EndToEndTests.php.phpunit.cache/ +.phpunit.result.cache From 2c1c16f179c626e6cd515e908874647f52176c4e Mon Sep 17 00:00:00 2001 From: tolu-paystack Date: Wed, 11 Feb 2026 11:44:06 +0100 Subject: [PATCH 2/6] chore: update dependencies for PHP 8.x compatibility --- composer.json | 11 ++++++----- phpunit.xml.dist | 27 +++++++++++---------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index 0846f6b..45e86fb 100644 --- a/composer.json +++ b/composer.json @@ -34,13 +34,14 @@ } ], "require": { + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~5.3", - "scrutinizer/ocular": "^1.1", - "guzzlehttp/guzzle": "^6.2", - "squizlabs/php_codesniffer": "^2.3", - "vlucas/phpdotenv": "^2.2" + "phpunit/phpunit": "^9.6 || ^10.0", + "scrutinizer/ocular": "^1.9", + "guzzlehttp/guzzle": "^7.0", + "squizlabs/php_codesniffer": "^3.7", + "vlucas/phpdotenv": "^5.5" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 55c11b1..104ab11 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,29 +1,24 @@ - + stopOnFailure="false" + cacheDirectory=".phpunit.cache"> tests - - + + src/ - - + + - - - - - + + From 51bdead5aba13f7e9d205f70be019611712e4ef7 Mon Sep 17 00:00:00 2001 From: tolu-paystack Date: Wed, 11 Feb 2026 11:45:07 +0100 Subject: [PATCH 3/6] test: update test classes for PHPUnit 10 --- tests/AutoloadTest.php | 2 +- tests/EventTest.php | 10 +++++----- tests/Exception/ApiExceptionTest.php | 2 +- tests/Exception/PaystackExceptionTest.php | 2 +- tests/Exception/ValidationExceptionTest.php | 2 +- tests/FeeTest.php | 2 +- tests/Helpers/CallerTest.php | 2 +- tests/Helpers/RouterTest.php | 2 +- tests/Http/RequestBuilderTest.php | 2 +- tests/Http/RequestTest.php | 2 +- tests/Http/ResponseTest.php | 2 +- tests/MetadataBuilderTest.php | 2 +- tests/Mock/EventTestDouble.php | 4 ++-- tests/PaystackTest.php | 4 ++-- tests/Routes/BalanceTest.php | 2 +- tests/Routes/BankTest.php | 2 +- tests/Routes/CustomerTest.php | 2 +- tests/Routes/DecisionTest.php | 2 +- tests/Routes/IntegrationTest.php | 2 +- tests/Routes/InvoiceTest.php | 2 +- tests/Routes/PageTest.php | 2 +- tests/Routes/PlanTest.php | 2 +- tests/Routes/SettlementTest.php | 2 +- tests/Routes/SubaccountTest.php | 2 +- tests/Routes/SubscriptionTest.php | 2 +- tests/Routes/TransactionTest.php | 2 +- tests/Routes/TransferTest.php | 2 +- tests/Routes/TransferrecipientTest.php | 2 +- 28 files changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/AutoloadTest.php b/tests/AutoloadTest.php index 4b67502..ae1d14f 100644 --- a/tests/AutoloadTest.php +++ b/tests/AutoloadTest.php @@ -1,7 +1,7 @@ assertEquals('nameee', $evt->discoverOwner([ - 'notme'=>'sk_live_inv4L1dinv4L1dinv4L1dinv4L1d', - 'notmeeither'=>'sk_test_inv4L1dinv4L1dinv4L1d', - 'nameee'=>'sk_test_inv4L1dinv4L1dinv4L1dinv4L1d', + 'notme'=>'sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + 'notmeeither'=>'sk_test_yyyyyyyyyyyyyyyyyyyyyyyy', + 'nameee'=>'sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', ])); } @@ -48,6 +48,6 @@ public function testForwardToRejectsInvalidUrl() public function testValidFor() { $evt = MockEvent::dummyCapture(); - $this->assertTrue($evt->validFor('sk_test_inv4L1dinv4L1dinv4L1dinv4L1d')); + $this->assertTrue($evt->validFor('sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')); } } diff --git a/tests/Exception/ApiExceptionTest.php b/tests/Exception/ApiExceptionTest.php index c18d0ef..ea9bffd 100644 --- a/tests/Exception/ApiExceptionTest.php +++ b/tests/Exception/ApiExceptionTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Exception\ApiException; -class ApiExceptionTest extends \PHPUnit_Framework_TestCase +class ApiExceptionTest extends \PHPUnit\Framework\TestCase { public function testInitialize() { diff --git a/tests/Exception/PaystackExceptionTest.php b/tests/Exception/PaystackExceptionTest.php index 98e79da..bcd57b1 100644 --- a/tests/Exception/PaystackExceptionTest.php +++ b/tests/Exception/PaystackExceptionTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Exception\PaystackException; -class PaystackExceptionTest extends \PHPUnit_Framework_TestCase +class PaystackExceptionTest extends \PHPUnit\Framework\TestCase { public function testInitialize() { diff --git a/tests/Exception/ValidationExceptionTest.php b/tests/Exception/ValidationExceptionTest.php index c529978..a212d6c 100644 --- a/tests/Exception/ValidationExceptionTest.php +++ b/tests/Exception/ValidationExceptionTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Exception\ValidationException; -class ValidationExceptionTest extends \PHPUnit_Framework_TestCase +class ValidationExceptionTest extends \PHPUnit\Framework\TestCase { public function testInitialize() { diff --git a/tests/FeeTest.php b/tests/FeeTest.php index b459f8d..db66ad3 100644 --- a/tests/FeeTest.php +++ b/tests/FeeTest.php @@ -3,7 +3,7 @@ use Yabacon\Paystack\Fee; -class FeeTest extends \PHPUnit_Framework_TestCase +class FeeTest extends \PHPUnit\Framework\TestCase { public function testAddFor() { diff --git a/tests/Helpers/CallerTest.php b/tests/Helpers/CallerTest.php index ad2a253..08a64c9 100644 --- a/tests/Helpers/CallerTest.php +++ b/tests/Helpers/CallerTest.php @@ -5,7 +5,7 @@ use Yabacon\Paystack; use Yabacon\Paystack\Contracts\RouteInterface; -class CallerTest extends \PHPUnit_Framework_TestCase +class CallerTest extends \PHPUnit\Framework\TestCase { public function testInitialize() { diff --git a/tests/Helpers/RouterTest.php b/tests/Helpers/RouterTest.php index 3f9d890..dc26c00 100644 --- a/tests/Helpers/RouterTest.php +++ b/tests/Helpers/RouterTest.php @@ -8,7 +8,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Exception\ValidationException; -class RouterTest extends \PHPUnit_Framework_TestCase +class RouterTest extends \PHPUnit\Framework\TestCase { public function testInitialize() { diff --git a/tests/Http/RequestBuilderTest.php b/tests/Http/RequestBuilderTest.php index de5eca9..7322385 100644 --- a/tests/Http/RequestBuilderTest.php +++ b/tests/Http/RequestBuilderTest.php @@ -7,7 +7,7 @@ use Yabacon\Paystack\Routes\Customer; use Yabacon\Paystack\Routes\Transaction; -class RequestBuilderTest extends \PHPUnit_Framework_TestCase +class RequestBuilderTest extends \PHPUnit\Framework\TestCase { public function testMoveArgsToSentargs() { diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php index fd68f4e..90579c9 100644 --- a/tests/Http/RequestTest.php +++ b/tests/Http/RequestTest.php @@ -5,7 +5,7 @@ use Yabacon\Paystack\Http\Response; use Yabacon\Paystack; -class RequestTest extends \PHPUnit_Framework_TestCase +class RequestTest extends \PHPUnit\Framework\TestCase { public function testInitialize() { diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php index 81b051d..aa5da5d 100644 --- a/tests/Http/ResponseTest.php +++ b/tests/Http/ResponseTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Http\Response; use Yabacon\Paystack\Exception\ApiException; -class ResponseTest extends \PHPUnit_Framework_TestCase +class ResponseTest extends \PHPUnit\Framework\TestCase { public function testInitialize() { diff --git a/tests/MetadataBuilderTest.php b/tests/MetadataBuilderTest.php index c505398..9df542a 100644 --- a/tests/MetadataBuilderTest.php +++ b/tests/MetadataBuilderTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\MetadataBuilder; use Yabacon\Paystack\Exception\BadMetaNameException; -class MetadataBuilderTest extends \PHPUnit_Framework_TestCase +class MetadataBuilderTest extends \PHPUnit\Framework\TestCase { public function testWith() { diff --git a/tests/Mock/EventTestDouble.php b/tests/Mock/EventTestDouble.php index e5a6c05..29ccc3e 100644 --- a/tests/Mock/EventTestDouble.php +++ b/tests/Mock/EventTestDouble.php @@ -17,8 +17,8 @@ class EventTestDouble extends \Yabacon\Paystack\Event . 'CUS_youwhistle","phone":null,"metadata":null,"risk_action":"default"},"tran' . 'saction":{"reference":"someref","status":"success","amount":150000,"currenc' . 'y":"NGN"},"created_at":"2017-01-13T00:00:02.000Z"}}'; - const DUMMY_SIGNATURE = '0ef509bb72218531a7d0aa58d2b8dcd93f63f7a4d1f8e2e36eadd' - . '4f0a19455a92f2ca57a57bfe98dc25f91c47b1221343d61fdd5fd2b7c41b8466cbe5ebd4974'; + const DUMMY_SIGNATURE = 'd955c6a79e1bc1e29aac57882ab71f8c2d93d8bf6f4fd51c3ce2fede2986124c' + . 'afbe657fc7655b20e6a9d4e684bfa875bdd55e577015a9532e9543c9c11c81a9'; public static function dummyCapture() { diff --git a/tests/PaystackTest.php b/tests/PaystackTest.php index 33d65c0..54013e0 100644 --- a/tests/PaystackTest.php +++ b/tests/PaystackTest.php @@ -6,7 +6,7 @@ use Yabacon\Paystack\Test\Mock\CustomRoute; use \Yabacon\Paystack\Exception\ValidationException; -class PaystackTest extends \PHPUnit_Framework_TestCase +class PaystackTest extends \PHPUnit\Framework\TestCase { public function testInitializeWithInvalidSecretKey() { @@ -16,7 +16,7 @@ public function testInitializeWithInvalidSecretKey() public function testVersion() { - $this->assertEquals("2.1.19", Paystack::VERSION); + $this->assertEquals("2.2.0", Paystack::VERSION); } public function testDisableFileGetContentsFallback() diff --git a/tests/Routes/BalanceTest.php b/tests/Routes/BalanceTest.php index 54847f3..fc3a64b 100644 --- a/tests/Routes/BalanceTest.php +++ b/tests/Routes/BalanceTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Balance; -class BalanceTest extends \PHPUnit_Framework_TestCase +class BalanceTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/BankTest.php b/tests/Routes/BankTest.php index 430ef08..3e5680c 100644 --- a/tests/Routes/BankTest.php +++ b/tests/Routes/BankTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Bank; -class BankTest extends \PHPUnit_Framework_TestCase +class BankTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/CustomerTest.php b/tests/Routes/CustomerTest.php index 47f9b54..30c06aa 100644 --- a/tests/Routes/CustomerTest.php +++ b/tests/Routes/CustomerTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Customer; -class CustomerTest extends \PHPUnit_Framework_TestCase +class CustomerTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/DecisionTest.php b/tests/Routes/DecisionTest.php index aacc2ca..6e62a74 100644 --- a/tests/Routes/DecisionTest.php +++ b/tests/Routes/DecisionTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Decision; -class DecisionTest extends \PHPUnit_Framework_TestCase +class DecisionTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/IntegrationTest.php b/tests/Routes/IntegrationTest.php index aa2f717..f634cc2 100644 --- a/tests/Routes/IntegrationTest.php +++ b/tests/Routes/IntegrationTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Integration; -class IntegrationTest extends \PHPUnit_Framework_TestCase +class IntegrationTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/InvoiceTest.php b/tests/Routes/InvoiceTest.php index 1a5fe93..f3b0343 100644 --- a/tests/Routes/InvoiceTest.php +++ b/tests/Routes/InvoiceTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Invoice; -class InvoiceTest extends \PHPUnit_Framework_TestCase +class InvoiceTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/PageTest.php b/tests/Routes/PageTest.php index 988fbc6..b70abb2 100644 --- a/tests/Routes/PageTest.php +++ b/tests/Routes/PageTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Page; -class PageTest extends \PHPUnit_Framework_TestCase +class PageTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/PlanTest.php b/tests/Routes/PlanTest.php index 94b1c35..c110b53 100644 --- a/tests/Routes/PlanTest.php +++ b/tests/Routes/PlanTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Plan; -class PlanTest extends \PHPUnit_Framework_TestCase +class PlanTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/SettlementTest.php b/tests/Routes/SettlementTest.php index 871a89a..faa2697 100644 --- a/tests/Routes/SettlementTest.php +++ b/tests/Routes/SettlementTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Settlement; -class SettlementTest extends \PHPUnit_Framework_TestCase +class SettlementTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/SubaccountTest.php b/tests/Routes/SubaccountTest.php index 0614ad5..5c685a5 100644 --- a/tests/Routes/SubaccountTest.php +++ b/tests/Routes/SubaccountTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Subaccount; -class SubaccountTest extends \PHPUnit_Framework_TestCase +class SubaccountTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/SubscriptionTest.php b/tests/Routes/SubscriptionTest.php index c0852ca..0bbb343 100644 --- a/tests/Routes/SubscriptionTest.php +++ b/tests/Routes/SubscriptionTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Subscription; -class SubscriptionTest extends \PHPUnit_Framework_TestCase +class SubscriptionTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/TransactionTest.php b/tests/Routes/TransactionTest.php index 3513616..6c3beb7 100644 --- a/tests/Routes/TransactionTest.php +++ b/tests/Routes/TransactionTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Transaction; -class TransactionTest extends \PHPUnit_Framework_TestCase +class TransactionTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/TransferTest.php b/tests/Routes/TransferTest.php index ce22e1b..97e83b3 100644 --- a/tests/Routes/TransferTest.php +++ b/tests/Routes/TransferTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Transfer; -class TransferTest extends \PHPUnit_Framework_TestCase +class TransferTest extends \PHPUnit\Framework\TestCase { public function testRoot() { diff --git a/tests/Routes/TransferrecipientTest.php b/tests/Routes/TransferrecipientTest.php index 4f3237c..4f40fdf 100644 --- a/tests/Routes/TransferrecipientTest.php +++ b/tests/Routes/TransferrecipientTest.php @@ -4,7 +4,7 @@ use Yabacon\Paystack\Contracts\RouteInterface; use Yabacon\Paystack\Routes\Transferrecipient; -class TransferrecipientTest extends \PHPUnit_Framework_TestCase +class TransferrecipientTest extends \PHPUnit\Framework\TestCase { public function testRoot() { From ef52b39a03dfc1a815003aa8b220f69d25b3b6cb Mon Sep 17 00:00:00 2001 From: tolu-paystack Date: Wed, 11 Feb 2026 11:45:25 +0100 Subject: [PATCH 4/6] fix: resolve PHP 8.3+ compatibility issues --- src/Paystack.php | 10 ++++++++-- src/Paystack/Helpers/Router.php | 2 +- src/Paystack/Http/Request.php | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Paystack.php b/src/Paystack.php index 8995b0e..71db762 100644 --- a/src/Paystack.php +++ b/src/Paystack.php @@ -12,7 +12,7 @@ class Paystack public $use_guzzle = false; public $custom_routes = []; public static $fallback_to_file_get_contents = true; - const VERSION="2.1.19"; + const VERSION="2.2.0"; public function __construct($secret_key) { @@ -42,9 +42,15 @@ public function useRoutes(array $routes) ); } + if (!class_exists($class)) { + throw new \InvalidArgumentException( + 'Custom route class ' . $class . ' does not exist.' + ); + } + if (! in_array(RouteInterface::class, class_implements($class))) { throw new \InvalidArgumentException( - 'Custom route class ' . $class . 'should implement ' . RouteInterface::class + 'Custom route class ' . $class . ' should implement ' . RouteInterface::class ); } } diff --git a/src/Paystack/Helpers/Router.php b/src/Paystack/Helpers/Router.php index 7fd2415..c0e7d5a 100644 --- a/src/Paystack/Helpers/Router.php +++ b/src/Paystack/Helpers/Router.php @@ -85,7 +85,7 @@ public function __construct($route, $paystackObj) $caller = new Caller($paystackObj); return $caller->callEndpoint($interface, $params, $sentargs); }; - $this->methods[$mtd] = \Closure::bind($mtdFunc, $this, get_class()); + $this->methods[$mtd] = \Closure::bind($mtdFunc, $this, static::class); } } diff --git a/src/Paystack/Http/Request.php b/src/Paystack/Http/Request.php index a4d3b43..55fb20f 100644 --- a/src/Paystack/Http/Request.php +++ b/src/Paystack/Http/Request.php @@ -110,7 +110,9 @@ public function attemptFileGetContents() ); $this->response->body = file_get_contents($this->endpoint, false, $context); if ($this->response->body === false) { - $this->response->messages[] = 'file_get_contents failed with response: \'' . error_get_last() . '\'.'; + $error = error_get_last(); + $errorMessage = $error ? ($error['message'] ?? 'Unknown error') : 'Unknown error'; + $this->response->messages[] = 'file_get_contents failed with response: \'' . $errorMessage . '\'.'; } else { $this->response->okay = true; } From 76b62ac5faf6ff10a0f1a616c6e8614fc3716e5d Mon Sep 17 00:00:00 2001 From: tolu-paystack Date: Wed, 11 Feb 2026 11:47:06 +0100 Subject: [PATCH 5/6] docs: add sample app --- examples/cactus-shop/.env.example | 2 + examples/cactus-shop/.gitignore | 1 + examples/cactus-shop/README.md | 33 ++++++++ examples/cactus-shop/public/callback.php | 79 +++++++++++++++++++ examples/cactus-shop/public/index.php | 99 ++++++++++++++++++++++++ examples/cactus-shop/public/webhook.php | 43 ++++++++++ examples/cactus-shop/src/helpers.php | 43 ++++++++++ 7 files changed, 300 insertions(+) create mode 100644 examples/cactus-shop/.env.example create mode 100644 examples/cactus-shop/.gitignore create mode 100644 examples/cactus-shop/README.md create mode 100644 examples/cactus-shop/public/callback.php create mode 100644 examples/cactus-shop/public/index.php create mode 100644 examples/cactus-shop/public/webhook.php create mode 100644 examples/cactus-shop/src/helpers.php diff --git a/examples/cactus-shop/.env.example b/examples/cactus-shop/.env.example new file mode 100644 index 0000000..b4cf912 --- /dev/null +++ b/examples/cactus-shop/.env.example @@ -0,0 +1,2 @@ +PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx +PAYSTACK_PUBLIC_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/examples/cactus-shop/.gitignore b/examples/cactus-shop/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/examples/cactus-shop/.gitignore @@ -0,0 +1 @@ +.env diff --git a/examples/cactus-shop/README.md b/examples/cactus-shop/README.md new file mode 100644 index 0000000..15dfb6f --- /dev/null +++ b/examples/cactus-shop/README.md @@ -0,0 +1,33 @@ +# Cactus Shop - Paystack PHP Sample Application + +A minimal checkout demo testing the `paystack-php` library. + +## Setup + +1. Copy and configure environment: + ```bash + cp .env.example .env + # Edit .env with your Paystack test keys + ``` + +2. Start the server: + ```bash + php -S localhost:8000 -t public + ``` + +3. Open http://localhost:8000 + +## Testing Webhooks + +1. Start ngrok: `ngrok http 8000` +2. Set webhook URL in Paystack Dashboard: `https://your-ngrok-url/webhook.php` +3. Webhook events are logged to `webhook.log` + +## Test Cards + +| Card Number | CVV | Result | +|-------------|-----|--------| +| 4084 0841 0841 0841 | 408 | Success | +| 4084 0841 0841 0842 | 408 | Failed | + +PIN: 0000 | OTP: 123456 diff --git a/examples/cactus-shop/public/callback.php b/examples/cactus-shop/public/callback.php new file mode 100644 index 0000000..248d58b --- /dev/null +++ b/examples/cactus-shop/public/callback.php @@ -0,0 +1,79 @@ +transaction->verify(['reference' => $reference]); + + if ($transaction->data->status === 'success') { + $status = 'success'; + $message = 'Payment successful! Your cactus is on its way 🌵'; + $transactionData = $transaction->data; + } else { + $status = 'failed'; + $message = 'Payment was not successful. Status: ' . $transaction->data->status; + } + } catch (\Yabacon\Paystack\Exception\ApiException $e) { + $status = 'error'; + $message = 'Verification failed: ' . $e->getMessage(); + } +} +?> + + + + + + Payment <?= ucfirst($status) ?> - Cactus Shop + + + +
+ +
✓

Thank You!

+ +
✗

Payment Failed

+ +
âš 

Error

+ +

+ +
+

Transaction Details

+
Referencereference) ?>
+
Amount₦amount / 100, 2) ?>
+
Emailcustomer->email) ?>
+
Paid Atpaid_at)) ?>
+
+ + Back to Shop +
+ + diff --git a/examples/cactus-shop/public/index.php b/examples/cactus-shop/public/index.php new file mode 100644 index 0000000..88e831e --- /dev/null +++ b/examples/cactus-shop/public/index.php @@ -0,0 +1,99 @@ +transaction->initialize([ + 'amount' => $cactusPrice, + 'email' => $email, + 'reference' => $reference, + 'callback_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/callback.php', + 'metadata' => [ + 'product' => 'Desert Cactus', + 'quantity' => 1 + ] + ]); + + header('Location: ' . $transaction->data->authorization_url); + exit; + + } catch (\Yabacon\Paystack\Exception\ApiException $e) { + $error = 'Payment initialization failed: ' . $e->getMessage(); + } + } +} + +?> + + + + + + Cactus Shop - Checkout + + + +
+
🌵
+
+

Desert Cactus

+
₦5,000
+

A beautiful low-maintenance succulent perfect for your home or office.

+
+
+ + +
+
+
Powered by Paystack
+
+ + diff --git a/examples/cactus-shop/public/webhook.php b/examples/cactus-shop/public/webhook.php new file mode 100644 index 0000000..fb4d8a5 --- /dev/null +++ b/examples/cactus-shop/public/webhook.php @@ -0,0 +1,43 @@ +validFor($secretKey)) { + logWebhook("ERROR: Invalid signature"); + exit; +} + +logWebhook("Signature valid"); +$eventType = $event->obj->event ?? 'unknown'; +logWebhook("Event type: $eventType"); + +switch ($eventType) { + case 'charge.success': + $data = $event->obj->data; + $reference = $data->reference ?? 'unknown'; + $amount = ($data->amount ?? 0) / 100; + $email = $data->customer->email ?? 'unknown'; + logWebhook("SUCCESS: Payment of ₦$amount received from $email (ref: $reference)"); + break; + case 'charge.failed': + $reference = $event->obj->data->reference ?? 'unknown'; + logWebhook("FAILED: Payment failed for reference $reference"); + break; + default: + logWebhook("Unhandled event type: $eventType"); +} + +logWebhook("Webhook processing complete"); diff --git a/examples/cactus-shop/src/helpers.php b/examples/cactus-shop/src/helpers.php new file mode 100644 index 0000000..e5e02be --- /dev/null +++ b/examples/cactus-shop/src/helpers.php @@ -0,0 +1,43 @@ + Date: Thu, 12 Feb 2026 11:59:17 +0100 Subject: [PATCH 6/6] docs: update README for PHP 7.4+ and add sample app reference --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 04507b3..5d7228b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A PHP API wrapper for [Paystack](https://paystack.co/). ## Requirements - Curl 7.34.0 or more recent (Unless using Guzzle) -- PHP 5.4.0 or more recent +- PHP 7.4.0 or more recent - OpenSSL v1.0.1 or more recent ## Install @@ -166,10 +166,10 @@ Generally, to make an API request after constructing a paystack object, Make a c to the resource/method thus: `$paystack->{resource}->{method}()`; for gets, use `$paystack->{resource}(id)` and to list resources: `$paystack->{resource}s()`. -Currently, we support: 'customer', 'page', 'plan', 'subscription', 'transaction' and 'subaccount'. Check +Currently, we support: 'customer', 'page', 'plan', 'subscription', 'transaction', 'subaccount', 'balance', 'bank', 'transfer', 'transferrecipient', 'invoice', 'settlement', 'integration' and 'decision'. Check our API reference([link-paystack-api-reference][link-paystack-api-reference]) for the methods supported. To specify parameters, send as an array. -Check [SAMPLES](SAMPLES.md) for more sample calls +Check [SAMPLES](SAMPLES.md) for more sample calls, or see the [examples/cactus-shop](examples/cactus-shop) directory for a complete working demo. ## Extras