pawaPay PHP SDK v4.4.0 adds dual V1/V2 support

pawaPay PHP SDK v4.4.0 adds dual V1/V2 support

posted 7 min read

pawaPay PHP SDK v4.4.0, Dual Version Support [V1 + V2], Seamless Migration

pawaPay helps teams accept and move money across African markets with a clean API surface. In v4.4.0 of the pawaPay PHP SDK, we introduced dual version support so V1 and V2 can live together in one codebase. Think of it as two gears turning in sync, V1 for stability, V2 for velocity.

This design gives you the freedom to adopt V2 where it shines, while keeping proven V1 flows earning in production.


Table of contents

  1. Why dual version support
  2. What changed in v4.4.0
  3. Environment setup and configuration
  4. Full runnable examples, V1 and V2
  5. Hosted Payment Page, end to end
  6. Verification flow, returnUrl and verify without cURL
  7. Testing and safety nets with PHPUnit
  8. Migration playbook, step by step
  9. Edge cases, country notes, and logging
  10. Troubleshooting guide
  11. Security notes and hardening tips
  12. Where to get it

1) Why dual version support

Upgrading payments is risky. Teams struggle with three realities:

  1. All or nothing switches increase outage risk.
  2. Two SDKs mean duplicate configs and twice the drift.
  3. Testing in production is scary without a clean rollback.

Our approach: keep V1 as the default, allow V2 to be toggled by environment or per call. You get gradual rollouts, quick comparisons, and instant fallback.


2) What changed in v4.4.0

  • Version aware client. Pass v1 or v2.
  • Hosted Payment Page usable from both versions.
  • Split config files for clarity: active_conf_v1.json, active_conf_v2.json, plus versioned MNO availability JSONs.
  • Better failure mapping via an improved FailureCodeHelper.
  • Cleaner errors that mention the version expectation.

3) Environment setup and configuration

Create a .env with clear tokens per environment.

# .env
ENVIRONMENT=sandbox
PAWAPAY_SANDBOX_API_TOKEN=your_sandbox_api_token_here
PAWAPAY_PRODUCTION_API_TOKEN=your_production_api_token_here

# Choose default API version across your app
PAWAPAY_API_VERSION=v1

Composer install:

composer require katorymnd/pawa-pay-integration

4) Full runnable examples, V1 and V2

A) Minimal V1 example, server side initiation

<?php
/**
 * Example: Minimal V1 deposit initiation
 * Goal, show a working flow with V1 as default.
 * Notes, reads ENV, builds a V1 client, initiates a classic deposit call.
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

if (!$apiToken) {
    http_response_code(500);
    echo 'Missing API token';
    exit;
}

$client = new ApiClient($apiToken, $environment, $sslVerify, 'v1');

// Payload shape may differ per version, keep V1 fields here
$resp = $client->initiateDeposit([
    'amount'      => '15000',
    'currency'    => 'UGX',
    'phoneNumber' => '256753456789',
    'mno'         => 'MTN',
    'reason'      => 'Order #1234',
]);

header('Content-Type: application/json');
echo json_encode($resp, JSON_PRETTY_PRINT);

B) Minimal V2 example, auto flow

<?php
/**
 * Example: Minimal V2 deposit initiation (auto)
 * Goal, exercise the new V2 route without touching V1 code.
 * Notes, just switch the version to v2.
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

if (!$apiToken) {
    http_response_code(500);
    echo 'Missing API token';
    exit;
}

$client = new ApiClient($apiToken, $environment, $sslVerify, 'v2');

// V2 supports initiateDepositAuto which may return a redirect, or a ready status
$resp = $client->initiateDepositAuto([
    'amount'      => '15000',
    'currency'    => 'UGX',
    'phoneNumber' => '256753456789',
    'mno'         => 'MTN',
    'returnUrl'   => 'https://example.com/paymentProcessed',
]);

header('Content-Type: application/json');
echo json_encode($resp, JSON_PRETTY_PRINT);

Why both examples matter
You can test V2 side by side, compare payloads and responses, and fall back to V1 with a single parameter change.


5) Hosted Payment Page, end to end

The hosted page is the fastest way to collect a payment with one redirect.

<?php
/**
 * Example: Hosted Payment Page, works with V1 or V2
 * Goal, show a clean redirect based on initiateDepositAuto.
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Katorymnd\PawaPayIntegration\Api\ApiClient;
use Katorymnd\PawaPayIntegration\Utils\Helpers;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiVersion  = getenv('PAWAPAY_API_VERSION') ?: 'v1';
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

if (!$apiToken) {
    http_response_code(500);
    echo 'Missing API token';
    exit;
}

$log = new Logger('pawaPay');
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_success.log', \Monolog\Level::Info));
$log->pushHandler(new StreamHandler(__DIR__ . '/../logs/payment_failed.log',  \Monolog\Level::Error));

$client = new ApiClient($apiToken, $environment, $sslVerify, $apiVersion);

// Inputs, in real apps take these from your form
$depositId            = Helpers::generateUniqueId();
$amount               = '15000';
$currency             = 'UGX';
$msisdn               = '256753456789';
$mno                  = 'MTN';
$returnUrl            = 'https://your-app.example/returnUrl.php';
$statementDescription = 'Order 1234';

// V2 will typically return a redirect URL for the hosted page
try {
    $resp = $client->initiateDepositAuto([
        'amount'      => $amount,
        'currency'    => $currency,
        'phoneNumber' => $msisdn,
        'mno'         => $mno,
        'returnUrl'   => $returnUrl,
        'reason'      => $statementDescription,
        'metadata'    => ['depositId' => $depositId],
    ]);

    if (!empty($resp['redirectUrl'])) {
        header('Location: ' . $resp['redirectUrl']);
        exit;
    }

    // If no redirect is required, log and show the result
    $log->info('Hosted page not required', ['resp' => $resp]);
    header('Content-Type: application/json');
    echo json_encode($resp, JSON_PRETTY_PRINT);
} catch (\Throwable $e) {
    $log->error('Hosted page error', ['ex' => $e->getMessage()]);
    http_response_code(500);
    echo 'Payment initiation failed';
}

6) Verification flow, returnUrl and verify without cURL

Many teams prefer not to make HTTP calls to a sibling script. You can include the verifier directly.

verify.php

<?php
/**
 * verify.php
 * Purpose, encapsulate verification logic that can be included from returnUrl.php
 * Input, expects $client and $transactionId to be in scope when included
 * Output, returns associative array $verification
 */

if (!isset($client) || !isset($transactionId)) {
    throw new \RuntimeException('verify.php expects $client and $transactionId');
}

$verification = $client->checkTransactionStatus([
    'transactionId' => $transactionId,
]);

// The parent script will read $verification

returnUrl.php

<?php
/**
 * returnUrl.php
 * Purpose, handle redirected users after payment, then verify internally
 */

require_once __DIR__ . '/../vendor/autoload.php';

use Dotenv\Dotenv;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

$dotenv = Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();

$environment = getenv('ENVIRONMENT') ?: 'sandbox';
$sslVerify   = ($environment === 'production');
$apiVersion  = getenv('PAWAPAY_API_VERSION') ?: 'v2';
$apiTokenKey = 'PAWAPAY_' . strtoupper($environment) . '_API_TOKEN';
$apiToken    = $_ENV[$apiTokenKey] ?? null;

$client = new ApiClient($apiToken, $environment, $sslVerify, $apiVersion);

// Transaction id from provider redirect or your metadata
$transactionId = $_GET['txId'] ?? $_POST['txId'] ?? null;

if (!$transactionId) {
    http_response_code(400);
    echo 'Missing transactionId';
    exit;
}

// Include verifier in-process, no cURL required
$verification = [];
require __DIR__ . '/verify.php';

// Persist result, show receipt, or redirect to success page
header('Content-Type: application/json');
echo json_encode(['verified' => true, 'result' => $verification], JSON_PRETTY_PRINT);

Why this is safe and simple
You keep the verification logic pure PHP, no extra HTTP hops, fewer moving parts, clearer stack traces.


7) Testing and safety nets with PHPUnit

A tiny guard to ensure your app respects the chosen version.

<?php
/**
 * tests/VersionSelectionTest.php
 * Purpose, assert the client honors PAWAPAY_API_VERSION, prevent regressions.
 */

use PHPUnit\Framework\TestCase;
use Katorymnd\PawaPayIntegration\Api\ApiClient;

final class VersionSelectionTest extends TestCase
{
    public function testChoosesV2WhenEnvSaysV2(): void
    {
        $client = new ApiClient('fake', 'sandbox', false, 'v2');
        $this->assertTrue(method_exists($client, 'initiateDepositAuto'), 'V2 method should exist');
    }

    public function testChoosesV1WhenEnvSaysV1(): void
    {
        $client = new ApiClient('fake', 'sandbox', false, 'v1');
        $this->assertTrue(method_exists($client, 'initiateDeposit'), 'V1 method should exist');
    }
}

Add more tests for response shape, error handling, and idempotency.


8) Migration playbook, step by step

  1. Inventory your live flows, deposit, payout, refund, reconciliation.
  2. Enable dual version globally with PAWAPAY_API_VERSION=v1.
  3. Stand up V2 in sandbox, run the minimal V2 example.
  4. Mirror payloads and compare responses, store both logs.
  5. Dark launch V2 for a small traffic slice, or specific markets.
  6. Monitor failure codes with FailureCodeHelper, alert on deltas.
  7. Flip a single product line to V2, observe for one full billing cycle.
  8. Rollback rule documented, set the env back to v1 if anything spikes.
  9. Expand V2 to more flows after two stable weeks.
  10. Retire V1 only when every flow is stable in V2.

Alternative to env flags
Use a feature flag service or a per request switch. Env is simple, flags give you finer control per tenant, market, or route.


9) Edge cases, country notes, and logging

  • MNO requirements differ. Some providers are strict on MSISDN formatting, always sanitize to digits and include country code.
  • Currencies vary. Lock your currency per market in config, do not trust form inputs.
  • Retrials. Use idempotent keys where available, or your own depositId.
  • Logs by version. Write to payment_v1.log and payment_v2.log so comparisons are easy.
  • Metadata discipline. Pass your order or cart reference to simplify reconciliation.

10) Troubleshooting guide

  • Missing API token, check .env names and ENVIRONMENT.
  • Method not found when calling V2, confirm you constructed the client with v2.
  • Unexpected currency error, confirm ISO code and market pairing.
  • Callback not firing, verify your returnUrl is public and uses HTTPS in production.
  • Random declines, try a different MNO in sandbox, then compare failure codes with FailureCodeHelper.

11) Security notes and hardening tips

  • Keep tokens outside your repo, use .env and secret managers.
  • Verify all incoming callbacks, do not rely only on query params, call checkTransactionStatus.
  • Store only what you need, mask MSISDN in logs, rotate tokens on schedule.
  • Enforce TLS, set sslVerify=true in production.

12) Where to get it

composer require katorymnd/pawa-pay-integration

Closing note

Dual version support is a calm bridge, not a cliff. Start with V1, test V2 where it counts, and move forward with confidence. If you want a diagram of the V1 to V2 rollout path, say the word and I will add a crisp visual.

0 votes
0 votes

More Posts

pawaPay SDK offers secure, user-friendly mobile money integration for PHP, ideal for African e-commerce.

katormya0 - Nov 25, 2024

ByteAether.Ulid v1.3.0: Enhanced ULID Generation Control and Security

Joonatan Uusväli - Jul 31

Ditch Your GUID Frustrations: Introducing ByteAether.Ulid v1.0.0 for .NET!

Joonatan Uusväli - Jul 9

Web Scraper (V1) - Python

Mayank Chawdhari - Nov 25

How to Create a One-Time Payment Link and Secure Webhook Integration with Stripe

Hamza - Oct 27
chevron_left