hopekelldev / laravel-payvessel
A Laravel package for interacting with Payvessel API
Requires
- php: ^8.2
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
README
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.