paylode/paylode-php

Official PHP SDK for Paylode Services Limited — CBN Licensed PSSP

Maintainers

Package info

github.com/Gokeakinboro/paylode-php

Homepage

Issues

pkg:composer/paylode/paylode-php

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

1.0.0 2026-06-02 06:07 UTC

This package is auto-updated.

Last update: 2026-07-02 07:06:19 UTC


README

Official PHP SDK for Paylode Services Limited — CBN Licensed Payment Solution Service Provider (PSSP).

Packagist Version PHP Version License: MIT

Requirements

  • PHP 7.4+
  • ext-curl and ext-json (standard in most PHP installs)
  • A Paylode secret key (sk_live_... or sk_test_...) — obtain from your merchant dashboard

Installation

composer require paylode/paylode-php

Zero external dependencies — uses PHP cURL extension only.

Quick start

use Paylode\Paylode;

$client = new Paylode('sk_live_xxxxxxxxxxxxxxxxxxxx');

// 1. Initialize a payment — redirect your customer to authorization_url
$txn = $client->transaction->initialize([
    'email'        => 'customer@example.com',
    'amount'       => 500000,       // ₦5,000 in kobo (1 kobo = 0.01 naira)
    'callback_url' => 'https://yoursite.com/payment/complete',
    'channels'     => ['card', 'bank_transfer'],
    'metadata'     => ['order_id' => 'ORD-9812', 'customer_name' => 'Ada Obi'],
]);
header('Location: ' . $txn['data']['authorization_url']);
exit;

// 2. Verify server-side when the customer returns — ALWAYS do this
$result = $client->transaction->verify($_GET['reference']);
if ($result['data']['status'] === 'success') {
    fulfillOrder($result['data']['metadata']['order_id']);
}

API reference

new Paylode($secretKey, $sandbox = null)

Parameter Type Description
$secretKey string Your sk_live_... or sk_test_... key (required)
$sandbox bool|null Force sandbox mode (auto-detected from key prefix)
$client = new Paylode('sk_live_xxxxxxxxxxxxxxxxxxxx');
var_dump($client->sandbox);      // bool(false) for live keys
echo $client->getVersion();      // '1.0.0'

$client->transaction

->initialize(array $params)array

Initialize a new payment. Redirect your customer to $result['data']['authorization_url'].

Key Type Required Description
email string Yes Customer email address
amount int Yes Amount in kobo (minimum ₦100 = 10000 kobo)
reference string No Unique transaction reference (auto-generated if omitted)
currency string No 'NGN' (default)
callback_url string No URL to redirect customer after payment
channels string[] No ['card', 'bank_transfer', 'ussd', 'direct_debit']
metadata array No Arbitrary key-value pairs — returned in webhooks
$txn = $client->transaction->initialize([
    'email'        => 'customer@example.com',
    'amount'       => 250000,                     // ₦2,500
    'reference'    => 'ORD-2026-00142',            // optional, must be unique
    'callback_url' => 'https://myshop.com/confirm',
    'channels'     => ['card', 'bank_transfer'],
    'metadata'     => ['order_id' => 'ORD-9812', 'plan' => 'premium'],
]);

$redirectUrl = $txn['data']['authorization_url'];  // redirect customer here
$reference   = $txn['data']['reference'];           // store this

->verify(string $reference)array

Verify a transaction. Always call this server-side before fulfilling any order.

$result = $client->transaction->verify('ORD-2026-00142');

if ($result['data']['status'] === 'success') {
    $amountPaid = $result['data']['amount'];   // in kobo
    $fees       = $result['data']['fees'];     // platform fees
    $orderId    = $result['data']['metadata']['order_id'];
    fulfillOrder($orderId);
}

->list(...)array

$transactions = $client->transaction->list(
    page: 1,
    perPage: 50,
    status: 'success',          // 'success' | 'failed' | 'pending'
    fromDate: '2026-01-01',
    toDate: '2026-01-31'
);
foreach ($transactions['data'] as $txn) {
    echo $txn['reference'] . ' — ₦' . ($txn['amount'] / 100) . PHP_EOL;
}

->fetch(string $transactionId)array

$txn = $client->transaction->fetch('txn_01JXYZ123456');

->refund(string $reference, ?int $amount = null, string $reason = '')array

// Full refund
$client->transaction->refund('ORD-2026-00142');

// Partial refund — ₦2,000
$client->transaction->refund('ORD-2026-00142', 200000, 'Item out of stock');

$client->customer

// Create
$customer = $client->customer->create([
    'email'      => 'ada@example.com',
    'first_name' => 'Ada',
    'last_name'  => 'Obi',
    'phone'      => '+2348012345678',
    'metadata'   => ['plan' => 'gold'],
]);

// Fetch by email or customer code
$c = $client->customer->fetch('ada@example.com');
$c = $client->customer->fetch('CUS_abc123');

// List
$customers = $client->customer->list(page: 1, perPage: 50);

// Update
$client->customer->update('CUS_abc123', ['phone' => '+2349011112222']);

$client->subaccount — for aggregators

Subaccounts represent merchants under an aggregator. Paylode uses them to automatically split settlement at the point of transaction.

// Register a merchant under your aggregator account
$sub = $client->subaccount->create([
    'business_name'     => 'Shoprite Nigeria',
    'settlement_bank'   => 'GTB',
    'account_number'    => '0123456789',
    'percentage_charge' => 70,       // merchant receives 70% of each transaction
    'description'       => 'Shoprite Lagos Island branch',
]);
$subaccountCode = $sub['data']['subaccount_code'];  // SUB_xxxx

// Use the subaccount code in a split transaction
$txn = $client->transaction->initialize([
    'email'      => 'buyer@example.com',
    'amount'     => 100000,
    'subaccount' => $subaccountCode,
    'metadata'   => ['order_id' => 'ORD-9812'],
]);
$client->subaccount->fetch('SUB_abc123');
$client->subaccount->list(page: 1, perPage: 50);
$client->subaccount->update('SUB_abc123', ['percentage_charge' => 75]);

$client->settlement

// List settlements
$settlements = $client->settlement->list(
    page: 1,
    perPage: 50,
    fromDate: '2026-01-01',
    toDate: '2026-01-31'
);

// Fetch one by ID
$s = $client->settlement->fetch('STL_01JX123456');

Paylode::verifyWebhook() — static

Verify a webhook signature from Paylode. Call at the top of every webhook handler before processing the event.

use Paylode\Paylode;

// In your webhook handler
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_PAYLODE_SIGNATURE'] ?? '';

if (!Paylode::verifyWebhook($raw, $sig, getenv('PAYLODE_WEBHOOK_SECRET'))) {
    http_response_code(401);
    exit;
}

$event = json_decode($raw, true);
$type  = $event['event'];   // e.g. 'payment.success', 'refund.processed'

switch ($type) {
    case 'payment.success':
        fulfillOrder($event['data']['metadata']['order_id']);
        break;
    case 'payment.failed':
        notifyCustomer($event['data']['customer']['email']);
        break;
    case 'refund.processed':
        updateOrderStatus($event['data']['reference'], 'refunded');
        break;
}

http_response_code(200);

Static helpers

Paylode::generateRef('ORD');         // 'ORD-60A3F2-9C4E1A2B'
Paylode::koboToNaira(500_000);       // 5000.0
Paylode::nairaToKobo(5000);          // 500000
$limits = Paylode::KYC_LIMITS;       // array of tier limits

KYC tier limits

$limits = Paylode::KYC_LIMITS;

$limits['tier_1']['single_txn'];  // 5_000_000 kobo (₦50,000)
$limits['tier_1']['daily'];        // 30_000_000 kobo (₦300,000)
$limits['tier_2']['single_txn'];  // 100_000_000 kobo (₦1,000,000)
$limits['tier_3']['single_txn'];  // 500_000_000 kobo (₦5,000,000)

Error handling

use Paylode\Exceptions\PaylodeValidationException;
use Paylode\Exceptions\PaylodeApiException;
use Paylode\Exceptions\PaylodeAuthException;
use Paylode\Exceptions\PaylodeException;

try {
    $txn = $client->transaction->initialize([
        'email'  => 'customer@example.com',
        'amount' => 250000,
    ]);
} catch (PaylodeValidationException $e) {
    // Invalid params — client-side, don't retry
    echo $e->getMessage();   // e.g. 'amount must be an integer in kobo...'
    echo $e->getField();     // e.g. 'amount'
} catch (PaylodeAuthException $e) {
    // Invalid or expired API key
    echo 'Check your PAYLODE_SECRET_KEY';
} catch (PaylodeApiException $e) {
    // API returned an error
    echo $e->getMessage();      // Human-readable error
    echo $e->getErrorCode();    // e.g. 'DUPLICATE_REFERENCE'
    echo $e->getStatusCode();   // HTTP status: 400, 422, 500...
    print_r($e->getRaw());      // Full API response array
} catch (PaylodeException $e) {
    // Network error or parse error
    echo 'Could not reach Paylode: ' . $e->getMessage();
}

Sandbox / test mode

// sk_test_ key auto-sets $client->sandbox = true
$client = new Paylode('sk_test_xxxxxxxxxxxxxxxxxxxx');
var_dump($client->sandbox);  // bool(true)

Test cards for the checkout page:

Card number Result
4084 0841 1111 1111 Success
4084 0841 1111 1112 Insufficient funds
4084 0841 1111 1113 Declined

Complete integration example (Laravel)

// routes/web.php
Route::post('/checkout',          [PaymentController::class, 'checkout']);
Route::get('/confirm',            [PaymentController::class, 'confirm']);
Route::post('/webhook/paylode',   [PaymentController::class, 'webhook'])->withoutMiddleware(VerifyCsrfToken::class);

// app/Http/Controllers/PaymentController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Paylode\Paylode;
use Paylode\Exceptions\PaylodeException;

class PaymentController extends Controller
{
    private Paylode $client;

    public function __construct()
    {
        $this->client = new Paylode(config('services.paylode.secret_key'));
    }

    public function checkout(Request $request)
    {
        try {
            $txn = $this->client->transaction->initialize([
                'email'        => $request->email,
                'amount'       => $request->amount,
                'callback_url' => route('payment.confirm'),
                'metadata'     => ['order_id' => $request->order_id],
            ]);
            return redirect($txn['data']['authorization_url']);
        } catch (PaylodeException $e) {
            return back()->withErrors(['payment' => $e->getMessage()]);
        }
    }

    public function confirm(Request $request)
    {
        $result = $this->client->transaction->verify($request->reference);
        if ($result['data']['status'] === 'success') {
            $this->fulfillOrder($result['data']['metadata']['order_id']);
            return redirect()->route('order.complete');
        }
        return redirect()->route('order.failed');
    }

    public function webhook(Request $request)
    {
        $raw = $request->getContent();
        $sig = $request->header('X-Paylode-Signature', '');

        if (!Paylode::verifyWebhook($raw, $sig, config('services.paylode.webhook_secret'))) {
            abort(401);
        }

        $event = json_decode($raw, true);

        match ($event['event']) {
            'payment.success'   => $this->fulfillOrder($event['data']['metadata']['order_id']),
            'refund.processed'  => $this->markRefunded($event['data']['reference']),
            default             => null,
        };

        return response('OK', 200);
    }
}

Running tests

# Built-in test runner
php tests/PaylodeTest.php

# With PHPUnit
composer require --dev phpunit/phpunit
vendor/bin/phpunit tests/

Paylode Services Limited · CBN/PAY/2024/001847 · docs.paylodeservices.com