laravel-payvessel maintained by hopekelldev
Laravel Payvessel
A Laravel package providing a clean, Facade-based integration with the Payvessel API. Supports virtual accounts, payments, transfers, identity verification, virtual card issuing, and wallets.
Requirements
- PHP >= 8.2
- Laravel >= 10.0
- Laravel HTTP Client (built-in from Laravel 7+)
Installation
composer require hopekelldev/laravel-payvessel
Configuration
Publish the config file
php artisan vendor:publish --tag=config --provider="HopekellDev\Payvessel\PayvesselServiceProvider"
Environment variables
Add the following to your .env file:
PAYVESSEL_API_KEY=your_api_key
PAYVESSEL_API_SECRET=your_api_secret
PAYVESSEL_BUSINESS_ID=your_business_id
PAYVESSEL_API_URL=https://api.payvessel.com
# Optional — comma-separated list of Payvessel webhook IPs (defaults to known IPs)
PAYVESSEL_WEBHOOK_IPS=3.255.23.38,162.246.254.36
Usage
The package is accessed via the Payvessel facade. Each category is a dedicated helper class returned by a method on the facade.
use HopekellDev\Payvessel\Facades\Payvessel;
Available Endpoints
Virtual Accounts
// Create a STATIC or DYNAMIC reserved virtual bank account.
// Provide either 'bvn' or 'nin' — not both.
Payvessel::virtualAccounts()->createVirtualAccount([
'email' => 'john@example.com',
'name' => 'John Doe',
'phoneNumber' => '08012345678',
'bankcode' => ['999991'],
'account_type' => 'STATIC',
'bvn' => '12345678901',
]);
// Get details of a virtual account by account number.
Payvessel::virtualAccounts()->getSingleVirtualAccount('1234567890');
// Update the BVN linked to a virtual account.
Payvessel::virtualAccounts()->accountBVNUpdate('1234567890', '12345678901');
Transactions (Payments)
// Initialize a new payment — returns a checkout URL.
Payvessel::transactions()->initializePayment([
'amount' => '5000.00',
'channels' => ['BANK_TRANSFER'],
'currency' => 'NGN',
'customer_name' => 'John Doe',
'customer_email' => 'john@example.com',
'customer_phone_number' => '08012345678',
'redirect_url' => 'https://yourapp.com/payment/callback',
]);
// Verify the status of a payment transaction by reference.
Payvessel::transactions()->verifyPayment('TXN_2024_001');
Transfers (Payouts)
// Retrieve the list of supported banks and their codes.
Payvessel::transfers()->getBankList();
// Resolve an account number to an account name before sending money.
Payvessel::transfers()->validateAccount('0123456789', '058');
// Send money from your wallet to a bank account.
Payvessel::transfers()->initiateTransfer([
'amount' => '15000.00',
'account_number' => '0123456789',
'bank_code' => '058',
'reference' => 'PAYOUT_001',
'narration' => 'Vendor payment',
]);
// Send multiple payouts in a single batch.
Payvessel::transfers()->bulkTransfer('PAYROLL_2026_03', [
['amount' => '15000.00', 'account_number' => '0123456789', 'bank_code' => '058', 'reference' => 'PAYROLL_EMP001'],
['amount' => '20000.00', 'account_number' => '0987654321', 'bank_code' => '120001', 'reference' => 'PAYROLL_EMP002'],
]);
// Check the status of a previously initiated transfer.
Payvessel::transfers()->transferStatus('PAYOUT_001', 'SESSION_123456789');
Identity Verification (KYC)
// Basic BVN verification — match fields against BVN records.
Payvessel::verification()->verifyBvnBasic([
'bvn' => '22123456789',
'first_name' => 'John',
'middle_name' => 'Adebayo',
'last_name' => 'Doe',
'gender' => 'MALE',
'birthday' => '1992-08-14',
'phone_number' => '08012345678',
]);
// Enhanced BVN verification — retrieve full BVN-linked identity profile.
Payvessel::verification()->verifyBvnEnhanced('22123456789');
// Basic NIN verification — match fields against NIN records.
Payvessel::verification()->verifyNinBasic([
'nin' => '12345678901',
'first_name' => 'John',
'middle_name' => 'Adebayo',
'last_name' => 'Doe',
'gender' => 'MALE',
'birthday' => '1992-08-14',
'phone_number' => '08012345678',
]);
// Enhanced NIN verification — retrieve full NIN-linked identity profile.
Payvessel::verification()->verifyNinEnhanced('12345678901');
// Verify a driver's license.
Payvessel::verification()->verifyDriversLicense('LAG-DL-4839201');
// Verify an international passport.
Payvessel::verification()->verifyPassport('A12345678');
// Verify a voter's card.
Payvessel::verification()->verifyVotersCard('AKD12345678901');
// Verify a bank account against a BVN.
Payvessel::verification()->verifyBankAccount('22123456789', '058', '0123456789');
// Compare two face images and return a similarity score.
Payvessel::verification()->compareFaces($sourceBase64, $targetBase64);
// Blacklist query using phone number, BVN, and NIN.
Payvessel::verification()->blacklistQuery([
'phone_number' => '08012345678',
'bvn_no' => '22123456789',
'nin' => '12345678901',
]);
// Credit score query.
Payvessel::verification()->creditScoreQuery('08012345678', '22123456789', [
'channel' => 'web',
'product' => 'consumer-loan',
]);
Virtual Card Issuing (USD)
// Create a USD virtual card for a customer (asynchronous — returns PENDING).
// Listen for a webhook or poll getCard() until status is ACTIVE.
Payvessel::virtualCards()->createCard([
'first_name' => 'Jane',
'last_name' => 'Doe',
'email' => 'jane@example.com',
'phone' => '08031234567',
'bvn' => '22345678901',
'dob' => '1990-05-15',
'brand' => 'VISA', // VISA or MASTERCARD
'currency' => 'USD',
'prefund_amount' => '10.00',
'card_name' => 'Jane Doe',
]);
// List all issued cards. Optionally filter by status.
Payvessel::virtualCards()->listCards();
Payvessel::virtualCards()->listCards('ACTIVE'); // PENDING | ACTIVE | FROZEN | TERMINATED | FAILED
// Retrieve a single card by ID (includes full card_number and cvv when ACTIVE/FROZEN).
Payvessel::virtualCards()->getCard('7f219a25-d968-4894-9a8b-ba83fa0bf6ec');
// Fund a card — debits business USD wallet and credits the card.
Payvessel::virtualCards()->fundCard('7f219a25-...', '25.00');
// Withdraw from a card — moves USD back to business wallet (min $3.00).
Payvessel::virtualCards()->withdrawFromCard('7f219a25-...', '5.00');
// Freeze a card — blocks all spending.
Payvessel::virtualCards()->freezeCard('7f219a25-...');
// Unfreeze a previously frozen card.
Payvessel::virtualCards()->unfreezeCard('7f219a25-...');
// Permanently terminate a card. Remaining balance returns to business wallet.
Payvessel::virtualCards()->terminateCard('7f219a25-...');
// Get transaction history for a card.
Payvessel::virtualCards()->getCardTransactions('7f219a25-...', size: 50);
// Calculate a fee before performing a card operation.
// Fee types: issuance | funding | withdrawal | spend | maintenance | cross_border | chargeback | decline
Payvessel::virtualCards()->feeQuote('funding', '50.00');
Wallets
// Get (or auto-create) the managed wallet for your business.
Payvessel::wallet()->getWallet();
// Retrieve the current available and ledger balance.
Payvessel::wallet()->getBalance();
Webhook Verification
Payvessel signs webhook payloads using HMAC-SHA512. Verify the signature and sender IP before processing any event.
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
public function webhook(Request $request): JsonResponse
{
$payload = $request->getContent();
$signature = $request->header('Payvessel-Http-Signature');
$allowedIps = config('payvessel.webhook_ips', []);
$expectedHash = hash_hmac('sha512', $payload, config('payvessel.api_secret'));
if ($signature !== $expectedHash || !in_array($request->ip(), $allowedIps)) {
return response()->json(['message' => 'Unauthorized'], 400);
}
$data = $request->json()->all();
// Handle the event...
return response()->json(['message' => 'success'], 200);
}
Available Methods — Quick Reference
| Category | Method | Description |
|---|---|---|
| Virtual Accounts | virtualAccounts()->createVirtualAccount($data) |
Create a STATIC or DYNAMIC reserved bank account |
virtualAccounts()->getSingleVirtualAccount($account) |
Get virtual account details | |
virtualAccounts()->accountBVNUpdate($account, $bvn) |
Update the BVN on a virtual account | |
| Transactions | transactions()->initializePayment($data) |
Initialize a payment and get a checkout URL |
transactions()->verifyPayment($reference) |
Verify payment status by reference | |
| Transfers | transfers()->getBankList() |
List supported banks and codes |
transfers()->validateAccount($account, $bankCode) |
Resolve account number to name | |
transfers()->initiateTransfer($data) |
Send money to a bank account | |
transfers()->bulkTransfer($batchRef, $transfers) |
Send multiple payouts in one batch | |
transfers()->transferStatus($reference, $sessionId) |
Check transfer status | |
| Verification | verification()->verifyBvnBasic($data) |
Match fields against a BVN |
verification()->verifyBvnEnhanced($bvn) |
Full BVN identity profile | |
verification()->verifyNinBasic($data) |
Match fields against a NIN | |
verification()->verifyNinEnhanced($nin) |
Full NIN identity profile | |
verification()->verifyDriversLicense($licenseNumber) |
Verify a driver's license | |
verification()->verifyPassport($passportNumber) |
Verify an international passport | |
verification()->verifyVotersCard($votersId) |
Verify a voter's card | |
verification()->verifyBankAccount($bvn, $bankCode, $account) |
Verify bank account against BVN | |
verification()->compareFaces($source, $target) |
Compare two face images | |
verification()->blacklistQuery($data) |
Blacklist check by phone/BVN/NIN | |
verification()->creditScoreQuery($mobile, $idNumber, $extendInfo) |
Credit score query | |
| Virtual Cards | virtualCards()->createCard($data) |
Create a USD virtual card (async) |
virtualCards()->listCards($status) |
List all issued cards | |
virtualCards()->getCard($cardId) |
Get a single card with full credentials | |
virtualCards()->fundCard($cardId, $amount) |
Fund a card from business wallet | |
virtualCards()->withdrawFromCard($cardId, $amount) |
Withdraw from card to wallet | |
virtualCards()->freezeCard($cardId) |
Block card spending | |
virtualCards()->unfreezeCard($cardId) |
Restore card spending | |
virtualCards()->terminateCard($cardId) |
Permanently close a card | |
virtualCards()->getCardTransactions($cardId, $size) |
Card transaction history | |
virtualCards()->feeQuote($feeType, $amountUsd) |
Calculate card operation fee | |
| Wallets | wallet()->getWallet() |
Get or create business wallet |
wallet()->getBalance() |
Get wallet available and ledger balance |
Coming in Next Update
The following categories are planned for the next release:
-
Biller Reseller — Resell airtime, data bundles, and betting top-ups
getBillers($category)— list available billersgetBillerItems($billerId)— list packages for a billervalidateRechargeAccount($data)— validate a customer recharge accountcreateOrder($data)— place a biller reseller ordergetOrder($orderId)— get an order by IDverifyOrder($merchantReference)— verify order status
-
Gift Cards — Purchase and deliver digital gift cards
listCountries()— list countries with gift card productsgetCountry($countryId)— get a specific countrylistOperators($countryId)— list gift card operators in a countrylistProducts($operatorId)— list products for an operatorpurchaseGiftCard($data)— purchase a gift cardgetOrder($orderId)— retrieve a gift card orderverifyOrder($merchantReference)— verify order status
-
eSIM — Issue and manage eSIM data packages
listRegions()— list supported regionslistPackages($filters)— browse eSIM packagescreateOrder($data)— purchase an eSIMgetOrder($orderId)— retrieve an eSIM order
License
This package is released under the MIT License.
Author
Ezenwa Hopekell
- GitHub: HopekellDev
- Email: hopekelltech@gmail.com
- Message on WhatsApp
Contributions & Issues
Feel free to submit a GitHub Issue or pull request for improvements or bug reports.
composer require hopekelldev/laravel-payvessel
Configuration
Publish Configuration File
Run the following command to publish the configuration file:
php artisan vendor:publish --tag=config --provider="HopekellDev\Payvessel\PayvesselServiceProvider"
Environment Variables
Add the following to your .env file:
PAYVESSEL_API_KEY=your_api_key
PAYVESSEL_API_SECRET=your_api_secret
PAYVESSEL_BUSINESS_ID=your_business_id
PAYVESSEL_API_URL=https://api.payvessel.com
Usage Example
Create a Virtual Account
use Payvessel;
$response = Payvessel::virtualAccounts()->createVirtualAccount([
'email' => 'johndoe@example.com',
'name' => 'JOHN DOE',
'phoneNumber' => '09012345678',
'bankcode' => ['999991'], // Example: PalmPay code
'account_type' => 'STATIC',
'bvn' => '12345678901', // Or 'nin' => '123456789'
]);
if (isset($response['status']) && $response['status'] === 'success') {
// Success logic
} else {
// Handle failure
}
Available Methods
| Category | Method | Description |
|---|---|---|
| Virtual Accounts | virtualAccounts()->createVirtualAccount($data) |
Create a reserved virtual account |
| Virtual Accounts | virtualAccounts()->getSingleVirtualAccount($account) |
Get virtual account details |
| Virtual Accounts | virtualAccounts()->accountBVNUpdate($account, $bvn) |
Update the BVN of a virtual account |
Example Controller Usage
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use HopekellDev\Payvessel\Facades\Payvessel;
class PayvesselController extends Controller
{
public function createVirtualAccount(Request $request): JsonResponse
{
$validated = $request->validate([
'email' => 'required|email',
'name' => 'required|string',
'phoneNumber' => 'required|string',
'bankcode' => 'required|array',
'account_type' => 'required|string|in:STATIC,DYNAMIC',
'bvn' => 'nullable|string',
'nin' => 'nullable|string',
]);
try {
$result = Payvessel::virtualAccounts()->createVirtualAccount($validated);
return response()->json($result, 201);
} catch (\InvalidArgumentException $e) {
return response()->json(['error' => $e->getMessage()], 422);
} catch (\Exception $e) {
return response()->json(['error' => 'Server Error'], 500);
}
}
public function getVirtualAccount($account): JsonResponse
{
try {
$result = Payvessel::virtualAccounts()->getSingleVirtualAccount($account);
return response()->json($result, 200);
} catch (\Exception $e) {
return response()->json(['error' => 'Server Error'], 500);
}
}
public function updateAccountBVN(Request $request, $account): JsonResponse
{
$validated = $request->validate([
'bvn' => 'required|string',
]);
try {
$result = Payvessel::virtualAccounts()->accountBVNUpdate($account, $validated['bvn']);
return response()->json($result, 200);
} catch (\Exception $e) {
return response()->json(['error' => 'Server Error'], 500);
}
}
/**
* Handle Payvessel Webhook
*/
public function webhook(Request $request): JsonResponse
{
if (!$request->isMethod('post')) {
return response()->json(['message' => 'Method not allowed'], 405);
}
$payload = $request->getContent();
$signature = $request->header('Payvessel-Http-Signature');
$ipAddress = $request->ip();
$allowedIps = ["3.255.23.38", "162.246.254.36"];
$secret = env("PAYVESSEL_API_SECRET");
$expectedHash = hash_hmac('sha512', $payload, $secret);
if ($signature !== $expectedHash || !in_array($ipAddress, $allowedIps)) {
return response()->json(['message' => 'Permission denied, invalid hash or IP address.'], 400);
}
$data = json_decode($payload, true);
if (
!$data ||
!isset($data['transaction']['reference'], $data['order']['amount'], $data['virtualAccount']['virtualAccountNumber'])
) {
return response()->json(['message' => 'Invalid payload structure'], 422);
}
$reference = $data['transaction']['reference'];
$amount = floatval($data['order']['amount']);
$virtualAccount = $data['virtualAccount']['virtualAccountNumber'];
$virtualBankAccount = StaticBankAccount::where('account_number', $virtualAccount)->first();
if (!$virtualBankAccount || !$virtualBankAccount->user_id) {
return response()->json(['message' => 'User not found'], 404);
}
$user = User::find($virtualBankAccount->user_id);
if (!$user) {
return response()->json(['message' => 'User not found'], 404);
}
if (Transaction::where('reference', $reference)->exists()) {
return response()->json(['message' => 'Transaction already exists'], 200);
}
$transactionData = [
'amount' => $amount,
'payment' => 'Wallet Funding',
'reference' => $reference,
'gateway_id' => 5,
];
Transaction::create([
'user_id' => $user->id,
'reference' => $reference,
'amount' => $amount,
'status' => 'successful',
'gateway_id' => 5,
]);
$user->increment('balance', $amount);
return response()->json(['message' => 'success'], 200);
}
}
License
This package is released under the MIT License.
Author
Ezenwa Hopekell
- GitHub: HopekellDev
- Email: hopekelltech@gmail.com
- Message Hopekell on Whatsapp
Contributions & Issues
Feel free to submit a GitHub Issue or pull request for improvements or bug reports.