hopekelldev/laravel-payvessel

A Laravel package for interacting with Payvessel API

Maintainers

Package info

github.com/HopekellDev/laravel-payvessel

pkg:composer/hopekelldev/laravel-payvessel

Transparency log

Statistics

Installs: 9

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.0.1 2026-07-06 13:51 UTC

This package is auto-updated.

Last update: 2026-07-06 13:52:19 UTC


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.

Latest Version on Packagist Total Downloads Scrutinizer Code Quality PHP Version Laravel Version Version

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 billers
    • getBillerItems($billerId) — list packages for a biller
    • validateRechargeAccount($data) — validate a customer recharge account
    • createOrder($data) — place a biller reseller order
    • getOrder($orderId) — get an order by ID
    • verifyOrder($merchantReference) — verify order status
  • Gift Cards — Purchase and deliver digital gift cards

    • listCountries() — list countries with gift card products
    • getCountry($countryId) — get a specific country
    • listOperators($countryId) — list gift card operators in a country
    • listProducts($operatorId) — list products for an operator
    • purchaseGiftCard($data) — purchase a gift card
    • getOrder($orderId) — retrieve a gift card order
    • verifyOrder($merchantReference) — verify order status
  • eSIM — Issue and manage eSIM data packages

    • listRegions() — list supported regions
    • listPackages($filters) — browse eSIM packages
    • createOrder($data) — purchase an eSIM
    • getOrder($orderId) — retrieve an eSIM order

License

This package is released under the MIT License.

Author

Ezenwa Hopekell

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

Contributions & Issues

Feel free to submit a GitHub Issue or pull request for improvements or bug reports.