From 30f786dbee0925d621330d7ff50a9a34ccdcf3aa Mon Sep 17 00:00:00 2001 From: Myckhel Date: Tue, 7 Jul 2026 01:08:47 +0100 Subject: [PATCH 1/4] feat: implement fluent facade API for Paystack support classes and add integration tests --- readme.md | 30 ++- src/Facades/Paystack.php | 54 +++++ src/Paystack.php | 418 +++++++++++++++++++++++++++++++++++++++ tests/PaystackTest.php | 88 +++++++++ 4 files changed, 589 insertions(+), 1 deletion(-) create mode 100644 tests/PaystackTest.php diff --git a/readme.md b/readme.md index 916d021..9461210 100644 --- a/readme.md +++ b/readme.md @@ -59,7 +59,35 @@ return [ ## Quick Usage -Call support classes directly: +You can call the support classes statically or use the fluent facade wrapper APIs: + +### 1. Fluent/Facade API Wrapper (Recommended for IDE Autocomplete) + +Using the `Paystack` facade, you can call any support endpoint dynamically as an instance method (supporting both singular and plural forms): + +```php +use Binkode\Paystack\Facades\Paystack; + +// Initialize a transaction +$init = Paystack::transactions()->initialize([ + "email" => "customer@example.com", + "amount" => 500000, // amount in kobo +]); + +// Verify a transaction +$verify = Paystack::transaction()->verify("reference_here"); + +// Create a customer +$customer = Paystack::customers()->create([ + "email" => "customer@example.com", + "first_name" => "Jane", + "last_name" => "Doe", +]); +``` + +### 2. Static Support API + +Alternatively, you can call methods directly on the support classes statically: ```php use Binkode\Paystack\Support\Transaction; diff --git a/src/Facades/Paystack.php b/src/Facades/Paystack.php index 574852e..7ff597b 100644 --- a/src/Facades/Paystack.php +++ b/src/Facades/Paystack.php @@ -4,6 +4,60 @@ use Illuminate\Support\Facades\Facade; +/** + * @method static \Binkode\Paystack\Support\ApplePay applePay() + * @method static \Binkode\Paystack\Support\ApplePay applePays() + * @method static \Binkode\Paystack\Support\BulkCharge bulkCharge() + * @method static \Binkode\Paystack\Support\BulkCharge bulkCharges() + * @method static \Binkode\Paystack\Support\Charge charge() + * @method static \Binkode\Paystack\Support\Charge charges() + * @method static \Binkode\Paystack\Support\ControlPanel controlPanel() + * @method static \Binkode\Paystack\Support\ControlPanel controlPanels() + * @method static \Binkode\Paystack\Support\Customer customer() + * @method static \Binkode\Paystack\Support\Customer customers() + * @method static \Binkode\Paystack\Support\DedicatedVirtualAccount dedicatedVirtualAccount() + * @method static \Binkode\Paystack\Support\DedicatedVirtualAccount dedicatedVirtualAccounts() + * @method static \Binkode\Paystack\Support\Dispute dispute() + * @method static \Binkode\Paystack\Support\Dispute disputes() + * @method static \Binkode\Paystack\Support\Invoice invoice() + * @method static \Binkode\Paystack\Support\Invoice invoices() + * @method static \Binkode\Paystack\Support\Miscellaneous miscellaneous() + * @method static \Binkode\Paystack\Support\Miscellaneous miscellaneouses() + * @method static \Binkode\Paystack\Support\Order order() + * @method static \Binkode\Paystack\Support\Order orders() + * @method static \Binkode\Paystack\Support\Page page() + * @method static \Binkode\Paystack\Support\Page pages() + * @method static \Binkode\Paystack\Support\Plan plan() + * @method static \Binkode\Paystack\Support\Plan plans() + * @method static \Binkode\Paystack\Support\Product product() + * @method static \Binkode\Paystack\Support\Product products() + * @method static \Binkode\Paystack\Support\Recipient recipient() + * @method static \Binkode\Paystack\Support\Recipient recipients() + * @method static \Binkode\Paystack\Support\Refund refund() + * @method static \Binkode\Paystack\Support\Refund refunds() + * @method static \Binkode\Paystack\Support\Settlement settlement() + * @method static \Binkode\Paystack\Support\Settlement settlements() + * @method static \Binkode\Paystack\Support\Split split() + * @method static \Binkode\Paystack\Support\Split splits() + * @method static \Binkode\Paystack\Support\SubAccount subAccount() + * @method static \Binkode\Paystack\Support\SubAccount subAccounts() + * @method static \Binkode\Paystack\Support\Subscription subscription() + * @method static \Binkode\Paystack\Support\Subscription subscriptions() + * @method static \Binkode\Paystack\Support\Terminal terminal() + * @method static \Binkode\Paystack\Support\Terminal terminals() + * @method static \Binkode\Paystack\Support\Transaction transaction() + * @method static \Binkode\Paystack\Support\Transaction transactions() + * @method static \Binkode\Paystack\Support\Transfer transfer() + * @method static \Binkode\Paystack\Support\Transfer transfers() + * @method static \Binkode\Paystack\Support\TransferControl transferControl() + * @method static \Binkode\Paystack\Support\TransferControl transferControls() + * @method static \Binkode\Paystack\Support\Verification verification() + * @method static \Binkode\Paystack\Support\Verification verifications() + * @method static \Binkode\Paystack\Support\VirtualTerminal virtualTerminal() + * @method static \Binkode\Paystack\Support\VirtualTerminal virtualTerminals() + * + * @see \Binkode\Paystack\Paystack + */ class Paystack extends Facade { /** diff --git a/src/Paystack.php b/src/Paystack.php index 0e09e71..f81f5ca 100644 --- a/src/Paystack.php +++ b/src/Paystack.php @@ -6,4 +6,422 @@ class Paystack { + public function applePay(): Support\ApplePay + { + return new Support\ApplePay(); + } + + public function applePays(): Support\ApplePay + { + return $this->applePay(); + } + + public function bulkCharge(): Support\BulkCharge + { + return new Support\BulkCharge(); + } + + public function bulkCharges(): Support\BulkCharge + { + return $this->bulkCharge(); + } + + public function charge(): Support\Charge + { + return new Support\Charge(); + } + + public function charges(): Support\Charge + { + return $this->charge(); + } + + public function controlPanel(): Support\ControlPanel + { + return new Support\ControlPanel(); + } + + public function controlPanels(): Support\ControlPanel + { + return $this->controlPanel(); + } + + public function customer(): Support\Customer + { + return new Support\Customer(); + } + + public function customers(): Support\Customer + { + return $this->customer(); + } + + public function dedicatedVirtualAccount(): Support\DedicatedVirtualAccount + { + return new Support\DedicatedVirtualAccount(); + } + + public function dedicatedVirtualAccounts(): Support\DedicatedVirtualAccount + { + return $this->dedicatedVirtualAccount(); + } + + public function dispute(): Support\Dispute + { + return new Support\Dispute(); + } + + public function disputes(): Support\Dispute + { + return $this->dispute(); + } + + public function invoice(): Support\Invoice + { + return new Support\Invoice(); + } + + public function invoices(): Support\Invoice + { + return $this->invoice(); + } + + public function miscellaneous(): Support\Miscellaneous + { + return new Support\Miscellaneous(); + } + + public function miscellaneouses(): Support\Miscellaneous + { + return $this->miscellaneous(); + } + + public function order(): Support\Order + { + return new Support\Order(); + } + + public function orders(): Support\Order + { + return $this->order(); + } + + public function page(): Support\Page + { + return new Support\Page(); + } + + public function pages(): Support\Page + { + return $this->page(); + } + + public function plan(): Support\Plan + { + return new Support\Plan(); + } + + public function plans(): Support\Plan + { + return $this->plan(); + } + + public function product(): Support\Product + { + return new Support\Product(); + } + + public function products(): Support\Product + { + return $this->product(); + } + + public function recipient(): Support\Recipient + { + return new Support\Recipient(); + } + + public function recipients(): Support\Recipient + { + return $this->recipient(); + } + + public function refund(): Support\Refund + { + return new Support\Refund(); + } + + public function refunds(): Support\Refund + { + return $this->refund(); + } + + public function settlement(): Support\Settlement + { + return new Support\Settlement(); + } + + public function settlements(): Support\Settlement + { + return $this->settlement(); + } + + public function split(): Support\Split + { + return new Support\Split(); + } + + public function splits(): Support\Split + { + return $this->split(); + } + + public function subAccount(): Support\SubAccount + { + return new Support\SubAccount(); + } + + public function subAccounts(): Support\SubAccount + { + return $this->subAccount(); + } + + public function subscription(): Support\Subscription + { + return new Support\Subscription(); + } + + public function subscriptions(): Support\Subscription + { + return $this->subscription(); + } + + public function terminal(): Support\Terminal + { + return new Support\Terminal(); + } + + public function terminals(): Support\Terminal + { + return $this->terminal(); + } + + public function transaction(): Support\Transaction + { + return new Support\Transaction(); + } + + public function transactions(): Support\Transaction + { + return $this->transaction(); + } + + public function transfer(): Support\Transfer + { + return new Support\Transfer(); + } + + public function transfers(): Support\Transfer + { + return $this->transfer(); + } + + public function transferControl(): Support\TransferControl + { + return new Support\TransferControl(); + } + + public function transferControls(): Support\TransferControl + { + return $this->transferControl(); + } + + public function verification(): Support\Verification + { + return new Support\Verification(); + } + + public function verifications(): Support\Verification + { + return $this->verification(); + } + + public function virtualTerminal(): Support\VirtualTerminal + { + return new Support\VirtualTerminal(); + } + + public function virtualTerminals(): Support\VirtualTerminal + { + return $this->virtualTerminal(); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call(string $method, array $parameters): mixed + { + $resolvedClass = $this->resolveSupportClass($method); + + if ($resolvedClass) { + return new $resolvedClass(); + } + + throw new \BadMethodCallException("Method {$method} does not exist on " . get_class($this)); + } + + /** + * Resolve method name to a support class. + * + * @param string $method + * @return class-string|null + */ + protected function resolveSupportClass(string $method): ?string + { + /** @var array $mappings */ + $mappings = [ + 'applepay' => Support\ApplePay::class, + 'applepays' => Support\ApplePay::class, + 'apple_pay' => Support\ApplePay::class, + 'apple_pays' => Support\ApplePay::class, + + 'bulkcharge' => Support\BulkCharge::class, + 'bulkcharges' => Support\BulkCharge::class, + 'bulk_charge' => Support\BulkCharge::class, + 'bulk_charges' => Support\BulkCharge::class, + + 'charge' => Support\Charge::class, + 'charges' => Support\Charge::class, + + 'controlpanel' => Support\ControlPanel::class, + 'controlpanels' => Support\ControlPanel::class, + 'control_panel' => Support\ControlPanel::class, + 'control_panels' => Support\ControlPanel::class, + + 'customer' => Support\Customer::class, + 'customers' => Support\Customer::class, + + 'dedicatedvirtualaccount' => Support\DedicatedVirtualAccount::class, + 'dedicatedvirtualaccounts' => Support\DedicatedVirtualAccount::class, + 'dedicated_virtual_account' => Support\DedicatedVirtualAccount::class, + 'dedicated_virtual_accounts' => Support\DedicatedVirtualAccount::class, + + 'dispute' => Support\Dispute::class, + 'disputes' => Support\Dispute::class, + + 'invoice' => Support\Invoice::class, + 'invoices' => Support\Invoice::class, + + 'miscellaneous' => Support\Miscellaneous::class, + 'miscellaneouses' => Support\Miscellaneous::class, + + 'order' => Support\Order::class, + 'orders' => Support\Order::class, + + 'page' => Support\Page::class, + 'pages' => Support\Page::class, + + 'plan' => Support\Plan::class, + 'plans' => Support\Plan::class, + + 'product' => Support\Product::class, + 'products' => Support\Product::class, + + 'recipient' => Support\Recipient::class, + 'recipients' => Support\Recipient::class, + + 'refund' => Support\Refund::class, + 'refunds' => Support\Refund::class, + + 'settlement' => Support\Settlement::class, + 'settlements' => Support\Settlement::class, + + 'split' => Support\Split::class, + 'splits' => Support\Split::class, + + 'subaccount' => Support\SubAccount::class, + 'subaccounts' => Support\SubAccount::class, + 'sub_account' => Support\SubAccount::class, + 'sub_accounts' => Support\SubAccount::class, + + 'subscription' => Support\Subscription::class, + 'subscriptions' => Support\Subscription::class, + + 'terminal' => Support\Terminal::class, + 'terminals' => Support\Terminal::class, + + 'transaction' => Support\Transaction::class, + 'transactions' => Support\Transaction::class, + + 'transfer' => Support\Transfer::class, + 'transfers' => Support\Transfer::class, + + 'transfercontrol' => Support\TransferControl::class, + 'transfercontrols' => Support\TransferControl::class, + 'transfer_control' => Support\TransferControl::class, + 'transfer_controls' => Support\TransferControl::class, + + 'verification' => Support\Verification::class, + 'verifications' => Support\Verification::class, + + 'virtualterminal' => Support\VirtualTerminal::class, + 'virtualterminals' => Support\VirtualTerminal::class, + 'virtual_terminal' => Support\VirtualTerminal::class, + 'virtual_terminals' => Support\VirtualTerminal::class, + ]; + + $key = strtolower(str_replace('_', '', $method)); + if (isset($mappings[$key])) { + return $mappings[$key]; + } + + $cleaned = str_replace(['_', '-'], '', $method); + + $studly = $this->studly($cleaned); + /** @var class-string $class */ + $class = "Binkode\\Paystack\\Support\\{$studly}"; + if (class_exists($class)) { + return $class; + } + + if (str_ends_with(strtolower($cleaned), 'es')) { + $singular = substr($cleaned, 0, -2); + $studlySingular = $this->studly($singular); + /** @var class-string $classSingularEs */ + $classSingularEs = "Binkode\\Paystack\\Support\\{$studlySingular}"; + if (class_exists($classSingularEs)) { + return $classSingularEs; + } + } + + if (str_ends_with(strtolower($cleaned), 's')) { + $singular = substr($cleaned, 0, -1); + $studlySingular = $this->studly($singular); + /** @var class-string $classSingularS */ + $classSingularS = "Binkode\\Paystack\\Support\\{$studlySingular}"; + if (class_exists($classSingularS)) { + return $classSingularS; + } + } + + return null; + } + + /** + * Convert value to studly case. + * + * @param string $value + * @return string + */ + protected function studly(string $value): string + { + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + return str_replace(' ', '', $value); + } } diff --git a/tests/PaystackTest.php b/tests/PaystackTest.php new file mode 100644 index 0000000..d2faab4 --- /dev/null +++ b/tests/PaystackTest.php @@ -0,0 +1,88 @@ +set('paystack.secret_key', 'sk_test_mockkey'); + } + + public function test_facade_resolves_singular_and_plural_methods(): void + { + $this->assertInstanceOf(Transaction::class, Paystack::transaction()); + $this->assertInstanceOf(Transaction::class, Paystack::transactions()); + + $this->assertInstanceOf(Customer::class, Paystack::customer()); + $this->assertInstanceOf(Customer::class, Paystack::customers()); + } + + public function test_dynamic_fallback_resolves_methods(): void + { + // Test direct mapping + $this->assertInstanceOf(DedicatedVirtualAccount::class, Paystack::dedicatedVirtualAccount()); + $this->assertInstanceOf(DedicatedVirtualAccount::class, Paystack::dedicatedVirtualAccounts()); + + // Test snake case + $this->assertInstanceOf(DedicatedVirtualAccount::class, Paystack::dedicated_virtual_account()); + $this->assertInstanceOf(DedicatedVirtualAccount::class, Paystack::dedicated_virtual_accounts()); + } + + public function test_invalid_method_throws_exception(): void + { + $this->expectException(BadMethodCallException::class); + Paystack::nonExistentEndpoint(); + } + + public function test_transactions_initialize_api_call(): void + { + Http::fake([ + 'https://api.paystack.co/transaction/initialize' => Http::response(['status' => true, 'data' => ['authorization_url' => 'https://checkout.paystack.com']], 200) + ]); + + $response = Paystack::transactions()->initialize([ + 'amount' => 10000, + 'email' => 'user@example.com' + ]); + + $this->assertTrue($response['status']); + $this->assertSame('https://checkout.paystack.com', $response['data']['authorization_url']); + + Http::assertSent(function ($request) { + return $request->url() === 'https://api.paystack.co/transaction/initialize' && + $request->method() === 'POST' && + $request['amount'] === 10000 && + $request['email'] === 'user@example.com'; + }); + } + + public function test_customers_create_api_call(): void + { + Http::fake([ + 'https://api.paystack.co/customer' => Http::response(['status' => true, 'data' => ['customer_code' => 'CUS_123']], 200) + ]); + + $response = Paystack::customers()->create([ + 'email' => 'customer@example.com', + 'first_name' => 'John', + 'last_name' => 'Doe' + ]); + + $this->assertTrue($response['status']); + $this->assertSame('CUS_123', $response['data']['customer_code']); + + Http::assertSent(function ($request) { + return $request->url() === 'https://api.paystack.co/customer' && + $request->method() === 'POST' && + $request['email'] === 'customer@example.com'; + }); + } +} From dd75afe847d996b85bd55073a76fc2a626b69548 Mon Sep 17 00:00:00 2001 From: Michael Ishola Date: Tue, 7 Jul 2026 01:19:14 +0100 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/Paystack.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Paystack.php b/src/Paystack.php index f81f5ca..4c9d842 100644 --- a/src/Paystack.php +++ b/src/Paystack.php @@ -91,10 +91,6 @@ public function miscellaneous(): Support\Miscellaneous return new Support\Miscellaneous(); } - public function miscellaneouses(): Support\Miscellaneous - { - return $this->miscellaneous(); - } public function order(): Support\Order { From ee07ac258ad3534e10b646f2a36b907c323ea0f3 Mon Sep 17 00:00:00 2001 From: Michael Ishola Date: Tue, 7 Jul 2026 01:19:35 +0100 Subject: [PATCH 3/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/Paystack.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Paystack.php b/src/Paystack.php index 4c9d842..613aca0 100644 --- a/src/Paystack.php +++ b/src/Paystack.php @@ -315,8 +315,6 @@ protected function resolveSupportClass(string $method): ?string 'invoices' => Support\Invoice::class, 'miscellaneous' => Support\Miscellaneous::class, - 'miscellaneouses' => Support\Miscellaneous::class, - 'order' => Support\Order::class, 'orders' => Support\Order::class, From 1f783b6e21f860b29e9b76a6307b3cd063403bc1 Mon Sep 17 00:00:00 2001 From: Michael Ishola Date: Tue, 7 Jul 2026 01:19:54 +0100 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/Facades/Paystack.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Facades/Paystack.php b/src/Facades/Paystack.php index 7ff597b..e97707a 100644 --- a/src/Facades/Paystack.php +++ b/src/Facades/Paystack.php @@ -22,8 +22,6 @@ * @method static \Binkode\Paystack\Support\Invoice invoice() * @method static \Binkode\Paystack\Support\Invoice invoices() * @method static \Binkode\Paystack\Support\Miscellaneous miscellaneous() - * @method static \Binkode\Paystack\Support\Miscellaneous miscellaneouses() - * @method static \Binkode\Paystack\Support\Order order() * @method static \Binkode\Paystack\Support\Order orders() * @method static \Binkode\Paystack\Support\Page page() * @method static \Binkode\Paystack\Support\Page pages()