bhekor / laravel-paymish
A Laravel package for integrating Paymish payment gateway API
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel package for the Paymish payment gateway. Covers transactions, transfers, virtual accounts, subscriptions, products, customers, and more.
Official API docs: https://api.paymish.com/api/user-service/external/v1/docs/
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
- Guzzle 7+
Installation
composer require bhekor/laravel-paymish
Laravel auto-discovers the service provider and Paymish facade. Publish the config when you need to customize defaults:
php artisan vendor:publish --tag="paymish-config"
Configuration
Add credentials to .env. Prefer OpenAPI keys when available; email/password also works:
PAYMISH_PUBLIC_KEY=your-public-key PAYMISH_SECRET_KEY=your-secret-key # OR PAYMISH_EMAIL=your-email@example.com PAYMISH_PASSWORD=your-password PAYMISH_BASE_URL=https://api.paymish.com PAYMISH_LIVE=false PAYMISH_TIMEOUT=30 PAYMISH_CURRENCY=NGN PAYMISH_TOKEN_CACHE_SECONDS=3500 PAYMISH_WEBHOOK_SECRET= PAYMISH_WEBHOOK_SIGNATURE_HEADER=x-paymish-signature
Amounts for initialize / DVA / transfers are in NGN major units (send 1100 for ₦1,100). OpenAPI may mention subunits; live Paymish treats the value as naira.
Security
- Never hardcode live card numbers, CVVs, or BVNs in application code.
- Prefer
initialize()+ Paymish hosted checkout over rawwithCard()when possible (PCI scope). - Examples below use sandbox/test placeholders only.
- Always re-verify payments server-side (
transaction()->verify()) and on webhooks — do not trust client redirects alone.
Quick start
use Bhekor\Paymish\Facades\Paymish; // Initialize (hosted / redirect flow) $response = Paymish::transaction()->initialize([ 'email' => 'customer@email.com', 'amount' => '10000', 'currency' => 'NGN', 'channels' => ['card', 'ussd', 'nqr', 'transfer'], 'callback_url' => 'https://your-site.com/callback', ]); // Verify after callback or webhook $response = Paymish::transaction()->verify('transaction-reference'); // data.status: completed | failed | pending | reversed
The client auto-authenticates from config before API calls (cached bearer JWT).
Usage
use Bhekor\Paymish\Facades\Paymish;
Authentication
Usually automatic. To generate a token manually:
$response = Paymish::auth()->generateToken([ 'public_key' => '...', 'secret_key' => '...', ]); // or: ['email' => '...', 'password' => '...']
Transactions
// Initialize with Dynamic Virtual Account $response = Paymish::transaction()->initializeDva([ 'email' => 'customer@email.com', 'amount' => '10000', 'reference' => 'unique-reference', ]); // Name enquiry (bank account) $response = Paymish::transaction()->verifyAccount([ 'bankCode' => '058', 'accountNumber' => '0123456789', ]); // List / refund $response = Paymish::transaction()->list(['status' => 'completed', 'page' => 1, 'limit' => 20]); $response = Paymish::transaction()->refund([ 'transactionRef' => 'TXN_...', 'amount' => 100, ]);
Channel helpers (test/sandbox data only):
Paymish::transaction()->withCard([/* customerId, amount, cardNumber, expiryYear, expiryMonth, cvv */]); Paymish::transaction()->withUssd(['customerId' => 8, 'amount' => 100]); Paymish::transaction()->withQr([/* customerId, amount, mch_no, sub_mch_no */]); Paymish::transaction()->withBank([/* customerId, amount, AccountNumber, DestinationInstitutionCode */]); Paymish::transaction()->withNdd([/* customerId, amount, ndd => [...] */]); Paymish::transaction()->verifyUssd(['ussdTransactionId' => '...', 'isLive' => false]); Paymish::transaction()->verifyBank([/* DestinationInstitutionCode, AccountNumber, isLive */]); Paymish::transaction()->getBanks(false);
Dedicated virtual accounts
$response = Paymish::dedicatedVirtualAccount()->create([ 'customerId' => 'cust_...', 'firstName' => 'John', 'lastName' => 'Doe', 'email' => 'customer@email.com', 'phoneNumber' => '08012345678', 'bvn' => '22222222226', // test placeholder 'dob' => '1990-01-15', 'address' => '123 Main Street, Lagos', 'gender' => 'Male', ]);
Virtual accounts
$response = Paymish::virtualAccount()->create([/* ... */]); $response = Paymish::virtualAccount()->list(['page' => 1, 'limit' => 20]); $response = Paymish::virtualAccount()->get($id); $response = Paymish::virtualAccount()->update($id, [/* ... */]);
Transfers
$response = Paymish::transfer()->getAllBanks(); $response = Paymish::transfer()->resolveAccount([ 'bankCode' => '058', 'accountNumber' => '0123456789', ]); $response = Paymish::transfer()->createSingle([ 'amount' => 1000, 'bankCode' => '999998', 'bankName' => 'Zenith Bank', 'accountNumber' => '0051762787', 'accountName' => 'Test Account', 'emailAddress' => 'email@example.com', 'transferNote' => 'Payment for invoice #1234', 'transferReference' => 'INV1234TRANS5678', ]); $response = Paymish::transfer()->getList(['status' => 'Successful', 'page' => 1, 'limit' => 10]); $response = Paymish::transfer()->getBalance(); $response = Paymish::transfer()->getDetails('TRF_...'); $response = Paymish::transfer()->verifyTransfer('TRF_...'); // prefers transfer code $response = Paymish::transfer()->submitBulk([/* bulk payload */]);
Recipients
$response = Paymish::recipient()->list(['limit' => 10, 'page' => 1]); $response = Paymish::recipient()->getDetails('RCP_...'); $response = Paymish::recipient()->delete('RCP_...'); $response = Paymish::recipient()->blacklist(['recipientId' => '...', 'isBlackListed' => true]);
Subscriptions & plans
$response = Paymish::plan()->create([ 'planName' => 'Premium Plan', 'planAmount' => '50.00', 'interval' => 'Monthly', 'planDescription' => 'Premium subscription plan', ]); $response = Paymish::plan()->list(['status' => 'Active', 'page' => 1, 'limit' => 10]); $response = Paymish::plan()->getDetails(1); $response = Paymish::plan()->updateDetails([/* ... */]); $response = Paymish::plan()->delete(['id' => 1]); $response = Paymish::subscription()->create([ 'planId' => 49, 'customerId' => 'CUST789', 'amount' => 29.99, 'interval' => 'monthly', 'status' => 'active', ]); $response = Paymish::subscription()->list(['status' => 'active', 'page' => 1, 'limit' => 10]); $response = Paymish::subscription()->get(1); $response = Paymish::subscription()->update(1, [/* ... */]); $response = Paymish::subscription()->delete(1);
Payment pages
$response = Paymish::paymentPage()->create([ 'pageType' => 'Subscription', 'pageName' => 'My Payment Page', 'pageDescription' => 'Sample payment page', 'planId' => 456, ]); $response = Paymish::paymentPage()->list(['status' => 'Published', 'page' => 1, 'limit' => 10]); $response = Paymish::paymentPage()->getDetails(1); $response = Paymish::paymentPage()->checkCustomLink(['customLink' => 'my-page']); $response = Paymish::paymentPage()->addProduct([/* pageId, productId, ... */]); $response = Paymish::paymentPage()->getProducts(1);
Products
$response = Paymish::product()->create([ 'productName' => 'Sample Product', 'productDescription' => 'This is a sample product', 'productPrice' => '100.00', 'quantityType' => 'Limited', 'quantityStock' => 100, ]); $response = Paymish::product()->list(['page' => 1, 'limit' => 10]); $response = Paymish::product()->getDetails('123'); $response = Paymish::product()->uploadMedia('123', '/path/to/image.jpg'); $response = Paymish::product()->addDigitalDownloadFile('123', '/path/to/file.pdf'); $response = Paymish::product()->addDiscountCode([ 'productId' => '123', 'code' => 'DISCOUNT20', 'discountPercentage' => 20, ]);
Additional product helpers (media, inventory, delivery fees, discount codes) follow the same facade pattern — see OpenAPI docs for full payloads.
Customers
$response = Paymish::customer()->create([ 'email' => 'john@example.com', 'first_name' => 'John', 'last_name' => 'Doe', 'phone_number' => '08012345678', ]); $response = Paymish::customer()->list(['limit' => 10, 'page' => 1, 'search' => 'John']); $response = Paymish::customer()->getById(1); $response = Paymish::customer()->update([/* ... */]); $response = Paymish::customer()->blacklist(['id' => 1, 'isBlackListed' => true]);
Business
$response = Paymish::business()->changeMode(true); // live mode $response = Paymish::business()->getDetails(); $response = Paymish::business()->list(); $response = Paymish::business()->create([ 'countryId' => 1, 'businessName' => 'My Business', 'businessCategory' => 'Retail', 'businessDescription' => 'Description', ]);
Split pages & subaccounts
$response = Paymish::splitPage()->create([ 'name' => 'Revenue Split', 'splitType' => 'percentage', ]); // Paymish API field name is `shere` (not `share`) $response = Paymish::splitPage()->addSubaccount(1, [ 'shere' => 50, 'sub_account_id' => 'ACCT_...', ]); $response = Paymish::subaccount()->create([ 'business_name' => 'My Business', 'account_number' => '1234567890', 'percentage_charge' => 10, 'currency' => 'NGN', 'bank' => 999004, 'account_name' => 'Account Name', ]); $response = Paymish::subaccount()->list(['page' => 1, 'limit' => 10]); $response = Paymish::subaccount()->get(1);
Disputes
$response = Paymish::dispute()->create([ 'image' => '/path/to/evidence.jpg', 'transactionRef' => 'TXN_...', 'disputeReason' => 'Product not received', 'disputeDescription' => 'Detailed description of the dispute', ]); $response = Paymish::dispute()->list(['limit' => 10, 'page' => 1]); $response = Paymish::dispute()->getDetails('TXN_...'); $response = Paymish::dispute()->update([/* ... */]);
Webhooks
Paymish OpenAPI does not document a webhook HMAC scheme. This package includes an optional Paystack-style helper (HMAC-SHA512 hex of the raw body).
If PAYMISH_WEBHOOK_SECRET is empty, PaymishWebhook::valid() returns true (no-op) — you must still verify the payment via the API.
use Bhekor\Paymish\Facades\Paymish; use Bhekor\Paymish\PaymishWebhook; use Illuminate\Http\Request; public function handle(Request $request) { $rawBody = $request->getContent(); $signature = $request->header(config('paymish.webhook_signature_header')); if (! PaymishWebhook::valid($rawBody, $signature)) { abort(401, 'Invalid webhook signature'); } $payload = $request->json()->all(); $reference = $payload['data']['reference'] ?? $payload['reference'] ?? null; if ($reference) { $verified = Paymish::transaction()->verify($reference); // fulfill order when $verified->successful() and status is completed } return response()->json(['received' => true]); }
Response handling
All API methods return an ApiResponse:
$response = Paymish::transaction()->initialize([/* ... */]); if ($response->successful()) { $data = $response->all(); $transactionId = $response->get('transactionId'); $nested = $response->get('data.status'); // dot notation supported $message = $response->message(); $status = $response->getStatusCode(); if ($response->has('data')) { $transactionData = $response->data(); } $array = $response->toArray(); }
Error handling
use Bhekor\Paymish\Exceptions\AuthenticationException; use Bhekor\Paymish\Exceptions\ValidationException; use Bhekor\Paymish\Exceptions\PaymishException; try { $response = Paymish::transaction()->initialize([/* ... */]); } catch (AuthenticationException $e) { // 401 / missing credentials } catch (ValidationException $e) { $errors = $e->getErrors(); } catch (PaymishException $e) { // 404, 5xx, and other API errors echo $e->getMessage(); }
Testing
composer test
Changelog
Please see CHANGELOG.md for recent changes.
Contributing
Contributions are welcome — open a pull request or issue.
Credits
License
The MIT License (MIT). Please see LICENSE.md for more information.