Date: 2024-11-21 || Views: 168
Dive deeper into advanced functionalities to optimize your payment processing.
After setting up and integrating the Pesapal PHP SDK into your application, it's time to explore its advanced features. Unlocking the full potential of the SDK allows you to optimize payment processing, enhance user experience, and streamline your operations. This comprehensive guide will walk you through the advanced functionalities, including recurring payments, refunds, detailed transaction queries, and more.
The Pesapal PHP SDK offers several advanced features:
Let's delve into each of these features and learn how to implement them.
Define the subscription plans within your application:
$orderData = json_encode([
"id" => "INV12345", // Unique identifier for the merchant reference
"currency" => "USD", // Payment currency
"amount" => 150.75, // Total payment amount
"description" => "Payment for invoice INV12345", // Description of the transaction
"callback_url" => "https://www.example.com/payment-callback", // URL to handle payment callbacks
"notification_id" => "87ad409f-f47d-49f0-b0ea-dd4ae4bdcc82", // Notification ID for tracking
"redirect_mode" => "PARENT_WINDOW", // Redirect mode for user navigation
"cancellation_url" => "https://www.example.com/payment-cancel", // URL for cancellations
"billing_address" => [
"phone_number" => "0700123456", // Customer's phone number
"email_address" => "[email protected]", // Customer's email
"country_code" => "US", // Country code
"first_name" => "Jane", // Customer's first name
"middle_name" => "Elizabeth", // Customer's middle name
"last_name" => "Doe", // Customer's last name
"line_1" => "456 Demo Avenue", // Address line 1
"line_2" => "Suite 789", // Address line 2
"city" => "San Francisco", // City
"state" => "CA", // State
"postal_code" => 94107 // Postal code
],
"account_number" => "123-456-789", // Account number associated with the transaction
"subscription_details" => [
"start_date" => "2024-11-21", // Subscription start date
"end_date" => "2024-12-31", // Subscription end date
"frequency" => "MONTHLY" // Billing frequency
]
]);
Use the SDK to initiate a recurring payment:
// Set content type to JSON
header("Content-Type: application/json");
// Include Composer's autoloader
$autoloadPath = __DIR__ . '/../vendor/autoload.php';
if (!file_exists($autoloadPath)) {
echo json_encode([
'success' => false,
'errorMessage' => 'Autoloader not found. Please run composer install.'
]);
exit;
}
require_once $autoloadPath;
// Include the libphonenumber library
use libphonenumber\PhoneNumberUtil;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\NumberParseException;
use Katorymnd\PesapalPhpSdk\Api\PesapalClient;
use Katorymnd\PesapalPhpSdk\Config\PesapalConfig;
use Katorymnd\PesapalPhpSdk\Exceptions\PesapalException;
use Katorymnd\PesapalPhpSdk\Utils\PesapalHelpers;
use Dotenv\Dotenv;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Whoops\Run;
use Whoops\Handler\PrettyPageHandler;
// Initialize Whoops error handler for development
$whoops = new Run();
$whoops->pushHandler(new PrettyPageHandler());
$whoops->register();
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
try {
// Retrieve consumer key and secret from environment variables
$consumerKey = $_ENV['PESAPAL_CONSUMER_KEY'] ?? null;
$consumerSecret = $_ENV['PESAPAL_CONSUMER_SECRET'] ?? null;
if (!$consumerKey || !$consumerSecret) {
throw new PesapalException('Consumer key or secret missing in environment variables.');
}
// Initialize PesapalConfig and PesapalClient
$configPath = __DIR__ . '/../pesapal_dynamic.json';
$config = new PesapalConfig($consumerKey, $consumerSecret, $configPath);
$environment = 'sandbox';
$sslVerify = false; // Enable SSL verification for production
$clientApi = new PesapalClient($config, $environment, $sslVerify);
// Initialize Monolog for logging
$log = new Logger('pawaPayLogger');
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_success.log', \Monolog\Level::Info));
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_failed.log', \Monolog\Level::Error));
// Get the raw POST data
$rawData = file_get_contents("php://input");
$data = json_decode($rawData, true);
if (!$data) {
throw new PesapalException('Invalid JSON data received.');
}
// Extract variables from $data
$amount = $data['amount'] ?? null;
$currency = $data['currency'] ?? 'USD';
$description = $data['description'] ?? null;
$emailAddress = $data['email_address'] ?? null;
$phoneNumber = $data['phone_number'] ?? null;
$merchantReference = PesapalHelpers::generateMerchantReference();
$accountNumber = $data['account_number'] ?? null;
$subscriptionDetails = $data['subscription_details'] ?? null;
// Validate required fields
if (!$amount || !$description) {
throw new PesapalException('Amount and description are required.');
}
// Validate contact information
if (!$emailAddress || !$phoneNumber) {
throw new PesapalException('Both email address and phone number must be provided.');
}
// Validate description length
if (strlen($description) > 100) {
throw new PesapalException('Description must be 100 characters or fewer.');
}
// Retrieve IPN details from dynamic config
$ipnDetails = $config->getIpnDetails();
$notificationId = $ipnDetails['notification_id'] ?? null;
if (!$notificationId) {
throw new PesapalException('Notification ID (IPN) is missing. Please configure IPN first.');
}
// Prepare order data
$orderData = [
"id" => $merchantReference,
"currency" => $currency,
"amount" => (float) $amount,
"description" => $description,
"callback_url" => "https://www.example.com/payment-callback",
"notification_id" => $notificationId,
"billing_address" => []
];
// Include contact information provided
if (!empty($emailAddress)) {
$orderData['billing_address']['email_address'] = $emailAddress;
}
if (!empty($phoneNumber)) {
// Use libphonenumber to parse and format the phone number into national format
$phoneUtil = PhoneNumberUtil::getInstance();
try {
// Parse the phone number in international format
$numberProto = $phoneUtil->parse($data['phone_number'], null);
// Format the number into national format (without country code)
$nationalNumber = $phoneUtil->format($numberProto, PhoneNumberFormat::NATIONAL);
// Remove any spaces, dashes, or parentheses
$nationalNumber = preg_replace('/[\s()-]/', '', $nationalNumber);
$orderData['billing_address']['phone_number'] = $nationalNumber;
} catch (NumberParseException $e) {
// Log the error
$log->error('Phone number parsing failed', [
'error' => $e->getMessage(),
'phone_number' => $data['phone_number']
]);
// Return an error response
throw new PesapalException('Invalid phone number format.');
}
}
// Include new billing details
if (isset($data['billing_details'])) {
$billingDetails = $data['billing_details'];
$orderData['billing_address']['country_code'] = $billingDetails['country'] ?? '';
$orderData['billing_address']['first_name'] = $billingDetails['first_name'] ?? '';
$orderData['billing_address']['middle_name'] = $billingDetails['middle_name'] ?? ''; // Assuming this field is available in $data
$orderData['billing_address']['last_name'] = $billingDetails['last_name'] ?? '';
$orderData['billing_address']['line_1'] = $billingDetails['address_line1'] ?? '';
$orderData['billing_address']['line_2'] = $billingDetails['address_line2'] ?? '';
$orderData['billing_address']['city'] = $billingDetails['city'] ?? '';
$orderData['billing_address']['state'] = $billingDetails['state'] ?? '';
$orderData['billing_address']['postal_code'] = $billingDetails['postal_code'] ?? '';
$orderData['billing_address']['zip_code'] = ''; // Assuming no specific field in $data, use blank
}
// Handle Recurring Payments
$isRecurring = isset($subscriptionDetails) && !empty($subscriptionDetails);
if ($isRecurring) {
if (!$accountNumber) {
throw new PesapalException('Account number is required for recurring payments.');
}
// Validate subscription details
$requiredSubscriptionFields = ['start_date', 'end_date', 'frequency'];
foreach ($requiredSubscriptionFields as $field) {
if (empty($subscriptionDetails[$field])) {
throw new PesapalException("The field '$field' is required in subscription details.");
}
}
// Validate date formats (assuming 'YYYY-MM-DD' format from the front-end)
$startDate = DateTime::createFromFormat('Y-m-d', $subscriptionDetails['start_date']);
$endDate = DateTime::createFromFormat('Y-m-d', $subscriptionDetails['end_date']);
if (!$startDate || !$endDate) {
throw new PesapalException('Invalid date format in subscription details. Use YYYY-MM-DD.');
}
if ($endDate <= $startDate) {
throw new PesapalException('End date must be after start date in subscription details.');
}
// Include recurring payment details with reformatted dates
$orderData['account_number'] = $accountNumber;
$orderData['subscription_type'] = 'AUTO'; // Include subscription_type
$orderData['subscription_details'] = [
'start_date' => $startDate->format('d-m-Y'), // Reformat date to 'DD-MM-YYYY'
'end_date' => $endDate->format('d-m-Y'), // Reformat date to 'DD-MM-YYYY'
'frequency' => $subscriptionDetails['frequency']
];
}
// Obtain a valid access token
$accessToken = $clientApi->getAccessToken();
if (!$accessToken) {
throw new PesapalException('Failed to obtain access token');
}
// Submit order request to Pesapal
$response = $clientApi->submitOrderRequest($orderData);
if ($response['status'] === 200 && isset($response['response']['redirect_url'])) {
$redirectUrl = $response['response']['redirect_url'];
$orderTrackingId = $response['response']['order_tracking_id'];
$log->info('Order submitted successfully', [
'redirect_url' => $redirectUrl,
'order_tracking_id' => $orderTrackingId,
'merchant_reference' => $merchantReference
]);
echo json_encode([
"success" => true,
"message" => "Order submitted successfully!",
"redirect_url" => $redirectUrl,
"order_tracking_id" => $orderTrackingId,
"merchant_reference" => $merchantReference
]);
} else {
// Handle error response
$errorResponse = $response['response']['error'] ?? 'Unknown error occurred during order submission.';
// If $errorResponse is an array, convert it to a string
if (is_array($errorResponse)) {
$errorMessage = $errorResponse['message'] ?? json_encode($errorResponse);
} else {
$errorMessage = $errorResponse;
}
$log->error('Order submission failed', [
'error' => $errorMessage,
'full_error_response' => $errorResponse,
'merchant_reference' => $merchantReference
]);
throw new PesapalException($errorMessage);
}
} catch (PesapalException $e) {
// Log the error to payment_failed.log
$log->error('Error in processing payment', [
'error' => $e->getMessage(),
'details' => $e->getErrorDetails()
]);
// Return the detailed error message
echo json_encode([
'success' => false,
'error' => $e->getMessage(),
'details' => $e->getErrorDetails()
]);
} catch (Exception $e) {
// Handle any unexpected exceptions
$log->error('Unexpected error', ['error' => $e->getMessage()]);
echo json_encode([
'success' => false,
'error' => 'An unexpected error occurred. Please try again later.'
]);
}
Embed the payment page using an iframe or redirect the user:
$redirectUrl = $iframeSrc;
<iframe src="<?php echo $iframeSrc; ?>" width="100%" height="700px" frameborder="0">
<p>Your browser does not support iframes.</p>
</iframe>
Set up an endpoint (payment-callback.php
) to handle IPN notifications:
<?php
// payment-callback used for making the refund logic
// Set content type to JSON
header('Content-Type: application/json');
// Include Composer's autoloader
$autoloadPath = __DIR__ . '/../vendor/autoload.php';
if (!file_exists($autoloadPath)) {
echo json_encode([
'success' => false,
'error' => 'Autoloader not found. Please run composer install.'
]);
exit;
}
require_once $autoloadPath;
// Use necessary namespaces
use Katorymnd\PesapalPhpSdk\Api\PesapalClient;
use Katorymnd\PesapalPhpSdk\Config\PesapalConfig;
use Katorymnd\PesapalPhpSdk\Exceptions\PesapalException;
use Dotenv\Dotenv;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Whoops\Run;
use Whoops\Handler\PrettyPageHandler;
// Initialize Whoops error handler for development
$whoops = new Run();
$whoops->pushHandler(new PrettyPageHandler());
$whoops->register();
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
try {
// Retrieve consumer key and secret from environment variables
$consumerKey = $_ENV['PESAPAL_CONSUMER_KEY'] ?? null;
$consumerSecret = $_ENV['PESAPAL_CONSUMER_SECRET'] ?? null;
if (!$consumerKey || !$consumerSecret) {
throw new PesapalException('Consumer key or secret missing in environment variables.');
}
// Initialize PesapalConfig and PesapalClient
$config = new PesapalConfig($consumerKey, $consumerSecret, __DIR__ . '/../pesapal_dynamic.json');
$clientApi = new PesapalClient($config, 'sandbox', false); // Change 'sandbox' to 'production' as necessary
// Initialize Monolog for logging
$log = new Logger('paymentCallbackLogger');
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_success.log', Logger::INFO));
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_failed.log', Logger::ERROR));
// Extract query parameters from the callback URL
$orderTrackingId = $_GET['OrderTrackingId'] ?? '';
$orderMerchantReference = $_GET['OrderMerchantReference'] ?? '';
if (empty($orderTrackingId) || empty($orderMerchantReference)) {
throw new PesapalException('Order Tracking ID and Merchant Reference are required.');
}
// Get the transaction status
$response = $clientApi->getTransactionStatus($orderTrackingId);
if ($response['status'] === 200 && isset($response['response'])) {
$transactionStatus = $response['response'];
// Log and output the transaction status
$log->info('Transaction status retrieved successfully', [
'orderTrackingId' => $orderTrackingId,
'transactionStatus' => $transactionStatus
]);
// Update your subscription records based on $transactionStatus
echo json_encode([
'success' => true,
'transactionStatus' => $transactionStatus
]);
} else {
$errorMessage = $response['response']['error'] ?? 'Unknown error occurred while retrieving transaction status.';
$log->error('Transaction status retrieval failed', [
'orderTrackingId' => $orderTrackingId,
'error' => $errorMessage
]);
throw new PesapalException($errorMessage);
}
} catch (PesapalException $e) {
$log->error('Error in payment callback', [
'error' => $e->getMessage()
]);
echo json_encode([
'success' => false,
'error' => $e->getMessage(),
'details' => $e->getTraceAsString()
]);
} catch (Exception $e) {
$log->error('Unexpected error', ['error' => $e->getMessage()]);
echo json_encode([
'success' => false,
'error' => 'An unexpected error occurred. Please try again later.'
]);
}
Use the SDK to request a refund:
<?php
// Set content type to JSON
header('Content-Type: application/json');
// Include Composer's autoloader
$autoloadPath = __DIR__ . '/../vendor/autoload.php';
if (!file_exists($autoloadPath)) {
echo json_encode([
'success' => false,
'error' => 'Autoloader not found. Please run composer install.'
]);
exit;
}
require_once $autoloadPath;
// Use necessary namespaces
use Katorymnd\PesapalPhpSdk\Api\PesapalClient;
use Katorymnd\PesapalPhpSdk\Config\PesapalConfig;
use Katorymnd\PesapalPhpSdk\Exceptions\PesapalException;
use Dotenv\Dotenv;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Whoops\Run;
use Whoops\Handler\PrettyPageHandler;
// Initialize Whoops error handler for development
$whoops = new Run();
$whoops->pushHandler(new PrettyPageHandler());
$whoops->register();
try {
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
// Retrieve consumer key and secret from environment variables
$consumerKey = $_ENV['PESAPAL_CONSUMER_KEY'] ?? null;
$consumerSecret = $_ENV['PESAPAL_CONSUMER_SECRET'] ?? null;
if (!$consumerKey || !$consumerSecret) {
throw new PesapalException('Consumer key or secret missing in environment variables.');
}
// Initialize PesapalConfig and PesapalClient
$configPath = __DIR__ . '/../pesapal_dynamic.json';
$config = new PesapalConfig($consumerKey, $consumerSecret, $configPath);
$environment = 'sandbox'; // or 'production' based on your environment
$sslVerify = false; // Set to true in production
$clientApi = new PesapalClient($config, $environment, $sslVerify);
// Initialize Monolog for logging
$log = new Logger('pawaPayLogger');
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_success.log', \Monolog\Level::Info));
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_failed.log', \Monolog\Level::Error));
// Get the raw POST data
$rawData = file_get_contents('php://input');
$data = json_decode($rawData, true);
if (!$data) {
throw new PesapalException('Invalid JSON data received.');
}
// Validate required fields
if (empty($data['order_tracking_id']) || empty($data['amount']) || empty($data['username']) || empty($data['remarks'])) {
throw new PesapalException('All fields are required.');
}
$orderTrackingId = $data['order_tracking_id'];
$refundAmount = $data['amount'];
$refundUsername = $data['username'];
$refundRemarks = $data['remarks'];
// Validate refund amount is a numeric value
if (!is_numeric($refundAmount) || $refundAmount <= 0) {
throw new PesapalException('Invalid refund amount. The amount must be a positive number.');
}
// Obtain a valid access token
$accessToken = $clientApi->getAccessToken();
if (!$accessToken) {
throw new PesapalException('Failed to obtain access token');
}
// Get the transaction status
$response = $clientApi->getTransactionStatus($orderTrackingId);
$responseData = []; // Initialize response data array
if ($response['status'] === 200 && isset($response['response'])) {
$transactionStatusData = $response['response'];
// Map status_code to status_message
$status_code = $transactionStatusData['status_code'] ?? null;
$status_messages = [
0 => 'INVALID',
1 => 'COMPLETED',
2 => 'FAILED',
3 => 'REVERSED',
];
$status_message = isset($status_messages[$status_code]) ? $status_messages[$status_code] : 'UNKNOWN STATUS';
// Log the transaction status
$log->info('Transaction status retrieved successfully', [
'order_tracking_id' => $orderTrackingId,
'status_code' => $status_code,
'status_message' => $status_message,
]);
// Collect transaction status data
$responseData['success'] = true;
$responseData['transaction_status'] = [
'payment_method' => $transactionStatusData['payment_method'] ?? null,
'amount' => $transactionStatusData['amount'] ?? null,
'created_date' => $transactionStatusData['created_date'] ?? null,
'confirmation_code' => $transactionStatusData['confirmation_code'] ?? null,
'order_tracking_id' => $transactionStatusData['order_tracking_id'] ?? null,
'payment_status_description' => $transactionStatusData['payment_status_description'] ?? null,
'description' => $transactionStatusData['description'] ?? null,
'message' => $transactionStatusData['message'] ?? null,
'payment_account' => $transactionStatusData['payment_account'] ?? null,
'call_back_url' => $transactionStatusData['call_back_url'] ?? null,
'status_code' => $status_code,
'status_message' => $status_message,
'merchant_reference' => $transactionStatusData['merchant_reference'] ?? null,
'account_number' => $transactionStatusData['account_number'] ?? null,
'payment_status_code' => $transactionStatusData['payment_status_code'] ?? null,
'currency' => $transactionStatusData['currency'] ?? null,
'error' => [
'error_type' => $transactionStatusData['error']['error_type'] ?? null,
'code' => $transactionStatusData['error']['code'] ?? null,
'message' => $transactionStatusData['error']['message'] ?? null
]
];
// Extract confirmation code for refund
$confirmationCode = $transactionStatusData['confirmation_code'] ?? null;
if ($confirmationCode) {
// Prepare refund data with user-provided values
$refundData = [
'confirmation_code' => $confirmationCode,
'amount' => $refundAmount,
'username' => $refundUsername,
'remarks' => $refundRemarks
];
try {
// Request refund
$refundResponse = $clientApi->requestRefund($refundData);
if ($refundResponse['status'] === 200 && isset($refundResponse['response'])) {
$refundDataResponse = $refundResponse['response'];
// Log the refund response
$log->info('Refund requested successfully', [
'refund_data' => $refundData,
'refund_response' => $refundDataResponse,
]);
// Add refund response to the output
$responseData['refund_response'] = $refundDataResponse;
} else {
$errorMessage = $refundResponse['response']['error']['message'] ?? 'Unknown error occurred while requesting refund.';
$log->error('Refund request failed', [
'error' => $errorMessage,
'refund_data' => $refundData,
]);
throw new PesapalException($errorMessage);
}
} catch (PesapalException $e) {
// Log the error
$log->error('Error in requesting refund', [
'error' => $e->getMessage(),
'details' => $e->getErrorDetails(),
'refund_data' => $refundData,
]);
// Add the error to the response
$responseData['refund_error'] = [
'error' => $e->getMessage(),
'details' => $e->getErrorDetails(),
];
}
} else {
// No confirmation code, cannot proceed with refund
$log->error('Confirmation code not available, cannot process refund.', [
'order_tracking_id' => $orderTrackingId,
]);
$responseData['refund_error'] = [
'error' => 'Confirmation code not available, cannot process refund.',
];
}
// Output the combined response
echo json_encode($responseData);
} else {
$errorMessage = $response['response']['error']['message'] ?? 'Unknown error occurred while retrieving transaction status.';
$log->error('Transaction status retrieval failed', [
'error' => $errorMessage,
'order_tracking_id' => $orderTrackingId
]);
throw new PesapalException($errorMessage);
}
} catch (PesapalException $e) {
// Log the error
$log->error('Error in checking transaction status', [
'error' => $e->getMessage(),
'details' => $e->getErrorDetails()
]);
// Return the detailed error message
echo json_encode([
'success' => false,
'error' => $e->getMessage(),
'details' => $e->getErrorDetails()
]);
} catch (Exception $e) {
// Handle any unexpected exceptions
$log->error('Unexpected error', ['error' => $e->getMessage()]);
echo json_encode([
'success' => false,
'error' => 'An unexpected error occurred. Please try again later.'
]);
}
Before processing a refund, it is crucial to verify the status of the transaction to ensure that it has been completed successfully. Only transactions that have achieved a 'COMPLETED' status are eligible for refunds. Once the transaction status is confirmed, the refund process can proceed with the specified amount. It is essential to ensure that the refund amount does not exceed the original transaction value, as each transaction can only be refunded once. This safeguard helps prevent errors and ensures the integrity of the transaction processing workflow.
Fetch comprehensive transaction details:
<?php
// check_transaction_status.php
// Set content type to JSON
header('Content-Type: application/json');
// Include Composer's autoloader
$autoloadPath = __DIR__ . '/../vendor/autoload.php';
if (!file_exists($autoloadPath)) {
echo json_encode([
'success' => false,
'error' => 'Autoloader not found. Please run composer install.'
]);
exit;
}
require_once $autoloadPath;
// Use necessary namespaces
use Katorymnd\PesapalPhpSdk\Api\PesapalClient;
use Katorymnd\PesapalPhpSdk\Config\PesapalConfig;
use Katorymnd\PesapalPhpSdk\Exceptions\PesapalException;
use Dotenv\Dotenv;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Whoops\Run;
use Whoops\Handler\PrettyPageHandler;
// Initialize Whoops error handler for development
$whoops = new Run();
$whoops->pushHandler(new PrettyPageHandler());
$whoops->register();
try {
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
// Retrieve consumer key and secret from environment variables
$consumerKey = $_ENV['PESAPAL_CONSUMER_KEY'] ?? null;
$consumerSecret = $_ENV['PESAPAL_CONSUMER_SECRET'] ?? null;
if (!$consumerKey || !$consumerSecret) {
throw new PesapalException('Consumer key or secret missing in environment variables.');
}
// Initialize PesapalConfig and PesapalClient
$configPath = __DIR__ . '/../pesapal_dynamic.json';
$config = new PesapalConfig($consumerKey, $consumerSecret, $configPath);
$environment = 'sandbox'; // or 'production' based on your environment
$sslVerify = false; // Set to true in production
$clientApi = new PesapalClient($config, $environment, $sslVerify);
// Initialize Monolog for logging
$log = new Logger('pawaPayLogger');
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_success.log', \Monolog\Level::Info));
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_failed.log', \Monolog\Level::Error));
// Get the raw POST data
$rawData = file_get_contents('php://input');
$data = json_decode($rawData, true);
if (!$data) {
throw new PesapalException('Invalid JSON data received.');
}
if (empty($data['order_tracking_id'])) {
throw new PesapalException('Order Tracking ID is required.');
}
$orderTrackingId = $data['order_tracking_id'];
// Obtain a valid access token
$accessToken = $clientApi->getAccessToken();
if (!$accessToken) {
throw new PesapalException('Failed to obtain access token');
}
// Get the transaction status
$response = $clientApi->getTransactionStatus($orderTrackingId);
if ($response['status'] === 200 && isset($response['response'])) {
$transactionStatusData = $response['response'];
// Map status_code to status_message
$status_code = $transactionStatusData['status_code'] ?? null;
$status_messages = [
0 => 'INVALID',
1 => 'COMPLETED',
2 => 'FAILED',
3 => 'REVERSED',
];
$status_message = isset($status_messages[$status_code]) ? $status_messages[$status_code] : 'UNKNOWN STATUS';
// Log the transaction status
$log->info('Transaction status retrieved successfully', [
'order_tracking_id' => $orderTrackingId,
'status_code' => $status_code,
'status_message' => $status_message,
]);
// Output all required transaction details, including status_message
echo json_encode([
'success' => true,
'transaction_status' => [
'payment_method' => $transactionStatusData['payment_method'] ?? null,
'amount' => $transactionStatusData['amount'] ?? null,
'created_date' => $transactionStatusData['created_date'] ?? null,
'confirmation_code' => $transactionStatusData['confirmation_code'] ?? null,
'order_tracking_id' => $transactionStatusData['order_tracking_id'] ?? null,
'payment_status_description' => $transactionStatusData['payment_status_description'] ?? null,
'description' => $transactionStatusData['description'] ?? null,
'message' => $transactionStatusData['message'] ?? null,
'payment_account' => $transactionStatusData['payment_account'] ?? null,
'call_back_url' => $transactionStatusData['call_back_url'] ?? null,
'status_code' => $status_code,
'status_message' => $status_message,
'merchant_reference' => $transactionStatusData['merchant_reference'] ?? null,
'account_number' => $transactionStatusData['account_number'] ?? null,
'payment_status_code' => $transactionStatusData['payment_status_code'] ?? null,
'currency' => $transactionStatusData['currency'] ?? null,
'error' => [
'error_type' => $transactionStatusData['error']['error_type'] ?? null,
'code' => $transactionStatusData['error']['code'] ?? null,
'message' => $transactionStatusData['error']['message'] ?? null
]
]
]);
} else {
$errorMessage = $response['response']['error']['message'] ?? 'Unknown error occurred while retrieving transaction status.';
$log->error('Transaction status retrieval failed', [
'error' => $errorMessage,
'order_tracking_id' => $orderTrackingId
]);
throw new PesapalException($errorMessage);
}
} catch (PesapalException $e) {
// Log the error
$log->error('Error in checking transaction status', [
'error' => $e->getMessage(),
'details' => $e->getErrorDetails()
]);
// Return the detailed error message
echo json_encode([
'success' => false,
'error' => $e->getMessage(),
'details' => $e->getErrorDetails()
]);
} catch (Exception $e) {
// Handle any unexpected exceptions
$log->error('Unexpected error', ['error' => $e->getMessage()]);
echo json_encode([
'success' => false,
'error' => 'An unexpected error occurred. Please try again later.'
]);
}
Use the retrieved data to:
Pesapal's payment page supports a variety of payment methods, including mobile money. When users are redirected to the payment page, they can choose from several options to complete their transactions. Ensure that mobile money options are enabled in your Pesapal settings to allow customers to utilize this feature effectively.
By mastering these advanced features of the Pesapal PHP SDK, you enhance your application's payment processing capabilities, providing a better experience for your users and greater control over transactions.
Discover how to enhance user interaction on your WordPress site...
Explore how Predictive UX and personalization are transforming web design...
Here is how I helped my clients reach their goals. Click on the portfolio websites.
A VS Code extension to manage and synchronize your remote and local files efficiently, supporting FTP, SFTP, SSH, WebDAV, and Google Drive connections.
A WordPress plugin that introduces a dynamic and interactive layer to your site, allowing users to express their feelings and thoughts on your content through a variety of reaction options.
The pawaPay SDK provides seamless mobile money integration into your PHP applications, enabling smooth transaction processing with powerful API features.
© Copyright 2025 - Katorymnd Web Solutions - All Rights Reserved. Registered with Uganda Registration Services Bureau.