laravel-paymish maintained by bhekor
Laravel Paymish
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.