Simple Pesepay PHP SDK Integration Tutorial

This is a beginner-friendly example showing how to integrate Pesepay payments into a PHP application using the official Pesepay PHP SDK.

We will cover:

  1. Installing the SDK

  2. Setting up Pesepay credentials

  3. Creating a redirect payment

  4. Checking payment status

  5. Handling return and result URLs


1. Install the Pesepay PHP SDK

First, install the SDK using Composer:

composer require codevirtus/pesepay

Make sure Composer has created the vendor folder in your project.


2. Project Structure

For this simple example, you can use the following structure:

pesepay-demo/
│
├── vendor/
├── config.php
├── checkout.php
├── return.php
├── result.php
└── check-payment.php


3. Testing Locally with ngrok

When testing Pesepay on your local machine, your callback URLs cannot be ordinary localhost URLs like:

http://localhost/pesepay-demo/result.php

This is because Pesepay needs to send requests to your resultUrl, and localhost is only accessible from your own computer.

To solve this, you can use ngrok. ngrok creates a public HTTPS URL that forwards requests to your local development server. This makes it useful for testing payment callbacks and webhooks locally. The official ngrok webhook guide explains the basic flow as: install ngrok, start your local webhook handler, run ngrok http <port>, then use the generated public URL as your webhook or callback URL.


Step 1: Download and Install ngrok

Go to the official ngrok website and create an account.

After logging in, download ngrok for your operating system:

  • Windows

  • macOS

  • Linux

You can download it from the ngrok dashboard or setup page. ngrok provides platform-specific setup instructions from its official site.

After downloading, extract the file and make sure the ngrok command can run from your terminal or command prompt.

To test if ngrok is installed, run:

ngrok version

If it shows the installed version, ngrok is ready.


Step 2: Connect ngrok to Your Account

In your ngrok dashboard, you will find an authentication token.

Copy the token and run:

ngrok config add-authtoken YOUR_NGROK_AUTH_TOKEN

Replace YOUR_NGROK_AUTH_TOKEN with the token from your ngrok account.


Step 3: Start Your Local PHP Server

Go into your project folder:

cd pesepay-demo

Start PHP’s built-in development server:

php -S localhost:8000

Your project should now be running locally at:

http://localhost:8000

For example, your checkout page should be accessible at:

http://localhost:8000/checkout.php


Step 4: Start ngrok

Open another terminal window and run:

ngrok http 8000

ngrok will generate a public forwarding URL that looks something like this:

https://abc123.ngrok-free.app

Use the HTTPS URL, not the HTTP one.


Step 5: Update Your Pesepay Callback URLs

Now update your config.php file and use the ngrok URL for your return and result URLs.

Example:

$pesepay->returnUrl = "https://abc123.ngrok-free.app/return.php";
$pesepay->resultUrl = "https://abc123.ngrok-free.app/result.php";

So your full callback setup in config.php should look like this:

$pesepay->returnUrl = "https://abc123.ngrok-free.app/return.php";
$pesepay->resultUrl = "https://abc123.ngrok-free.app/result.php";

Now Pesepay can reach your local result.php file through the ngrok public URL.


Step 6: Test the Payment Flow

Open this URL in your browser:

http://localhost:8000/checkout.php

The payment flow should now work like this:

Customer opens checkout.php locally
        ↓
Your app creates a Pesepay transaction
        ↓
Customer is redirected to Pesepay
        ↓
Pesepay redirects customer back to the ngrok returnUrl
        ↓
Pesepay sends the payment result to the ngrok resultUrl
        ↓
ngrok forwards the request to your local result.php file


Important ngrok Notes

Every time you restart ngrok, the free URL may change.

So if your old URL was:

https://abc123.ngrok-free.app

and ngrok gives you a new URL like:

https://xyz789.ngrok-free.app

you must update your config.php again:

$pesepay->returnUrl = "https://xyz789.ngrok-free.app/return.php";
$pesepay->resultUrl = "https://xyz789.ngrok-free.app/result.php";

Also make sure your PHP server is still running. ngrok only forwards traffic to your local server; it does not start PHP for you.

4. Create config.php

This file will contain your Pesepay credentials and shared setup.

Replace the test values with your actual credentials from Pesepay.

<?php

require_once 'vendor/autoload.php';

use Codevirtus\Payments\Pesepay;

$integrationKey = "YOUR_INTEGRATION_KEY";
$encryptionKey = "YOUR_ENCRYPTION_KEY";

$pesepay = new Pesepay($integrationKey, $encryptionKey);

// For local testing with ngrok
$pesepay->returnUrl = "https://YOUR_NGROK_URL/return.php";
$pesepay->resultUrl = "https://YOUR_NGROK_URL/result.php";

// For production, use your real domain instead
// $pesepay->returnUrl = "https://yourdomain.com/return.php";
// $pesepay->resultUrl = "https://yourdomain.com/result.php";

Important

Your returnUrl and resultUrl should not be localhost URLs when testing live payments. Pesepay must be able to reach your result URL from the internet.


5. Create checkout.php

This file creates a payment and redirects the customer to the Pesepay checkout page.

<?php

require_once 'config.php';

// Example order details
$amount = 10.00;
$currencyCode = "USD";
$paymentReason = "Website Order Payment";
$merchantReference = "ORDER-" . time();

try {
    $transaction = $pesepay->createTransaction(
        $amount,
        $currencyCode,
        $paymentReason,
        $merchantReference
    );

    $response = $pesepay->initiateTransaction($transaction);

    if ($response->success()) {
        $referenceNumber = $response->referenceNumber();
        $pollUrl = $response->pollUrl();
        $redirectUrl = $response->redirectUrl();

        /*
         * In a real application, save these values in your database:
         *
         * - merchantReference
         * - referenceNumber
         * - pollUrl
         * - amount
         * - currencyCode
         * - payment status, for example "PENDING"
         */

        header("Location: " . $redirectUrl);
        exit;
    } else {
        echo "Payment initiation failed: " . $response->message();
    }

} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

At this point, opening checkout.php in the browser should create a Pesepay transaction and redirect the user to the Pesepay checkout page.


6. Create return.php

This is the page the customer is redirected to after attempting payment.

Do not mark the order as paid just because the user landed on this page. Always verify the payment status first.

<?php

echo "<h2>Payment Received</h2>";
echo "<p>Thank you. Your payment is being verified.</p>";
echo "<p>Please wait while we confirm your transaction.</p>";

The return URL is mainly for the customer experience. The actual confirmation should be done by checking the payment status using the reference number or poll URL.


7. Create result.php

This is the endpoint Pesepay calls with the payment result.

In many integrations, this endpoint is used to update your order status. The exact data sent to this URL may depend on your Pesepay configuration, so you should log the incoming request first when testing.

<?php

// Log the incoming callback for debugging
$payload = file_get_contents("php://input");

file_put_contents(
    "pesepay-result-log.txt",
    date("Y-m-d H:i:s") . PHP_EOL .
    "GET: " . json_encode($_GET) . PHP_EOL .
    "POST: " . json_encode($_POST) . PHP_EOL .
    "BODY: " . $payload . PHP_EOL .
    "------------------------" . PHP_EOL,
    FILE_APPEND
);

http_response_code(200);

echo "Result received";

Once you understand what Pesepay is sending to your result URL, you can update your database accordingly.

However, the safest approach is still to verify the payment status using the SDK before marking an order as paid.


8. Create check-payment.php

This file shows how to check if a payment has been completed.

You should use the referenceNumber or pollUrl that was returned when the payment was created.

<?php

require_once 'config.php';

// Example only.
// In a real application, get this from your database.
$referenceNumber = "PESEPAY_REFERENCE_NUMBER_HERE";

try {
    $response = $pesepay->checkPayment($referenceNumber);

    if ($response->success()) {
        if ($response->paid()) {
            echo "Payment was successful.";

            /*
             * Update your database:
             * - Set order status to PAID
             * - Save final transaction details
             * - Deliver the product or service
             */
        } else {
            echo "Payment is not yet complete.";
        }
    } else {
        echo "Could not check payment: " . $response->message();
    }

} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

You can also check the payment using the poll URL:

<?php

require_once 'config.php';

// Example only.
// In a real application, get this from your database.
$pollUrl = "PESEPAY_POLL_URL_HERE";

try {
    $response = $pesepay->pollTransaction($pollUrl);

    if ($response->success()) {
        if ($response->paid()) {
            echo "Payment was successful.";
        } else {
            echo "Payment is not yet complete.";
        }
    } else {
        echo "Could not poll transaction: " . $response->message();
    }

} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}


Optional: Seamless Payment Example

Redirect payments are usually easier for beginners. But if you want customers to pay directly inside your application, you can use seamless payments.

Example for mobile money:

<?php

require_once 'config.php';

try {
    $payment = $pesepay->createPayment(
        "USD",
        "ECOCASH",
        "customer@example.com",
        "0712345678",
        "John Doe"
    );

    $requiredFields = [
        "customerPhoneNumber" => "0712345678"
    ];

    $response = $pesepay->makeSeamlessPayment(
        $payment,
        "Online Transaction",
        10.00,
        $requiredFields,
        "ORDER-" . time()
    );

    if ($response->success()) {
        echo "Payment created successfully.<br>";
        echo "Reference Number: " . $response->referenceNumber() . "<br>";
        echo "Poll URL: " . $response->pollUrl() . "<br>";

        /*
         * Save referenceNumber and pollUrl in your database.
         * Then use checkPayment() or pollTransaction() to verify payment.
         */
    } else {
        echo "Payment failed: " . $response->message();
    }

} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}


Common Mistakes Developers Make

1. Using localhost for callback URLs

This will not work for live callbacks:

$pesepay->resultUrl = "http://localhost/result.php";

Use a real public URL instead:

$pesepay->resultUrl = "https://yourdomain.com/result.php";


2. Not saving the reference number

When PesePay returns a successful initiation response, always save:

$referenceNumber = $response->referenceNumber();
$pollUrl = $response->pollUrl();

You need these values later to verify the payment.


3. Marking orders as paid too early

Do not mark an order as paid immediately after redirecting the customer.

Only mark the order as paid after:

$response->paid()

returns true.


4. Not handling failed responses

Always check:

if ($response->success()) {
    // Continue
} else {
    echo $response->message();
}


5. Not storing merchant references

Use a unique merchant reference for each order:

$merchantReference = "ORDER-" . time();

In production, it is better to use your actual order ID:

$merchantReference = "ORDER-" . $orderId;


Basic Payment Flow

The recommended flow is:

Customer clicks Pay
        ↓
Your system creates a Pesepay transaction
        ↓
PesePay returns referenceNumber, pollUrl, and redirectUrl
        ↓
Save referenceNumber and pollUrl in your database
        ↓
Redirect customer to redirectUrl
        ↓
Customer completes payment
        ↓
Customer returns to returnUrl
        ↓
PesePay sends result to resultUrl
        ↓
Your system verifies payment using checkPayment() or pollTransaction()
        ↓
If paid() is true, mark the order as PAID


Final Notes

For beginners, start with redirect payments first because they are simpler to implement.

Once redirect payments are working, you can move on to seamless payments for mobile money or card payments.

Always remember:

$response->success()

means the request was processed successfully.

But:

$response->paid()

means the customer has actually paid.

Do not confuse the two.