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:
-
Installing the SDK
-
Setting up Pesepay credentials
-
Creating a redirect payment
-
Checking payment status
-
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.