Date: 2024-11-21 || Views: 168

Mastering Advanced Features: Unlock the Full Potential of Pesapal SDK

Dive deeper into advanced functionalities to optimize your payment processing.


Introduction

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.

Prerequisites

  1. Successful integration of the Pesapal PHP SDK into your PHP application.
  2. Pesapal merchant account with API credentials (Consumer Key and Consumer Secret).
  3. Familiarity with basic SDK operations like initiating payments and handling callbacks.
  4. Basic understanding of PHP and object-oriented programming.

Advanced Features Overview

The Pesapal PHP SDK offers several advanced features:

  1. Recurring Payments: Automate billing for subscription-based services.
  2. Refund Processing: Manage full or partial refunds directly from your application.
  3. Detailed Transaction Queries: Retrieve comprehensive transaction details for auditing and reporting.

 

Let's delve into each of these features and learn how to implement them.


1. Implementing Recurring Payments

Step 1: Set Up Subscription Plans

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
    ]
]);

 

Step 2: Create a Recurring Payment Request

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.'

    ]);

}

 

Step 3: Embed or Redirect to the Payment Page

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>

Step 4: Handle Recurring Payment Notifications

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.'
    ]);
}

2. Processing Refunds

Step 1: Initiate a Refund Request

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.

 

3. Retrieving Detailed Transaction Information

Step 1: Query Transaction Details

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.'

    ]);

}

 

Step 2: Utilize Data for Reporting

Use the retrieved data to:

  1. Generate financial reports
  2. Audit transaction histories
  3. Analyze payment trends

4. Integrating Mobile Money Payments

Step 1: Enable Mobile Money Options

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.

Best Practices for Advanced Features

  1. Secure Data Handling: Encrypt sensitive data like payment tokens.
  2. Error Handling: Implement robust error checking and exception handling.
  3. Logging: Maintain detailed logs for all transactions and actions.

 

Conclusion

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.

 

Additional Resources

  1. Pesapal SDK Documentation: Detailed guides and API references.
  2. Submit an Issue: Report bugs or request features.
  3. Test Cards Page: Access test cards for payment simulations.


Discover how to enhance user interaction on your WordPress site...


Explore how Predictive UX and personalization are transforming web design...


Explore how Katorymnd leads in Uganda's web dev scene with...


Katorymnd Portfolio

Here is how I helped my clients reach their goals. Click on the portfolio websites.

Fishman's Fresh Fish Delivery

A custom-built PHP website for online fresh tilapia home delivery.

Online interracial local singles dating site.

Drupal website built for online dating.

Remote File Sync - VS Code Extension

A VS Code extension to manage and synchronize your remote and local files efficiently, supporting FTP, SFTP, SSH, WebDAV, and Google Drive connections.

Katorymnd Reaction Process - WordPress Plugin

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.

pawaPay SDK - Payment Integration

The pawaPay SDK provides seamless mobile money integration into your PHP applications, enabling smooth transaction processing with powerful API features.

Pesapal SDK - Payment Gateway Integration

A robust PHP SDK for integrating with Pesapal's payment gateway, providing seamless transaction handling for card payments



Get started now