serenity_technologies/gtbghana-partner-api

GTBGhana Partner API

Maintainers

Package info

gitlab.com/serenity-technologies-library/gtbghana-partner-api

Issues

pkg:composer/serenity_technologies/gtbghana-partner-api

Statistics

Installs: 99

Dependents: 0

Suggesters: 0

Stars: 1

v2.0.1 2026-03-30 23:44 UTC

README

Latest Version on Packagist Total Downloads

A Laravel package to integrate with GTB Ghana's Partner API, allowing you to access various financial services like airtime topup, bill payments, bank transfers, and more.

Installation

You can install the package via composer:

  composer require serenity_technologies/gtbghana-partner-api

Quick Setup

After installing the package, you can quickly publish all necessary files with the install command:

php artisan gtb-api:install

This command will publish:

  • Configuration file to config/partner-api.php
  • Migration files to database/migrations/
  • Bank model to app/Models/

Manual Configuration

Alternatively, you can manually publish the files:

After installing the package, you need to publish the configuration file:

  php artisan vendor:publish --provider="SerenityTechnologies\GtbGhana\PartnerApi\PartnerApiServiceProvider" --tag="config"

This will create a config/partner-api.php file. You can also set your configuration values in your .env file:

GTB_PARTNER_API_ENV=live 
GTB_PARTNER_SECRET_KEY=your_secret_key_here 
GTB_PARTNER_MERCHANT_ID=your_merchant_id_here 
GTB_PARTNER_MERCHANT_KEY=your_merchant_key_here

Database Setup

To use the bank database feature, you need to run the migrations:

php artisan vendor:publish --provider="SerenityTechnologies\GtbGhana\PartnerApi\PartnerApiServiceProvider" --tag="migrations" 
php artisan migrate

If you want to customize the Bank model, you can publish it to your application:

php artisan vendor:publish --provider="SerenityTechnologies\GtbGhana\PartnerApi\PartnerApiServiceProvider" --tag="models"

Configuration Options

  • GTB_PARTNER_API_ENV: Set to either test or live (default: test)
  • GTB_PARTNER_SECRET_KEY: Your secret key for generating secure hashes
  • GTB_PARTNER_MERCHANT_ID: Your merchant ID for API authentication
  • GTB_PARTNER_MERCHANT_KEY: Your merchant key for API authentication

Usage

With Facade (Recommended)

First, make sure to import the facade:

use SerenityTechnologies\GtbGhana\PartnerApi\Facades\PartnerApi;

Get Available Services

This api call returns a list of services available on Partner API To make a request to the Partner API, you can use the PartnerApi facade: Then, you can use the facade to make requests to the Partner API:

    $response = PartnerApi::getServices('2022011800004');
    or 
    $response = PartnerApi::getServices(uniquePartnerTransId());
    or
    $response = PartnerApi::getServices(timeBasedUniqueInt());

Query Customer Details

This api call allows you to lookup or verify customer details on a service on Partner API To query customer details, you can use the PartnerApi facade:

Query Definitions
ParameterRequiredDetails
PartnerTransIdYESUnique Id or reference for request
Type: Numeric
Max length 20.
ServiceNameYESService Name of the service being accessed. (Found in response of the services endpoint)
ServiceNumberYESThe account number / identifier of service being verified
Eg. Bank account Number, Mobile Money Wallet Number, Student ID.
SecondaryServiceNumberNORequired for specific Services that need extra data for verification. eg. For Bank Transfer: ServiceNumber is the account number, SecondaryServiceNumber is the bank code as specified in the banks list ( to get banks list, use the services endpoint with banks as service name)
$response = PartnerApi::query(
     '2022011800007851', // PartnerTransId
     'banktrf', // ServiceName
     '0013014480189501', // ServiceNumber
     '300312'
 ); // SecondaryServiceNumber (optional) );

Make a Payment

This api call allows you to pay for a service or complete a merchant transaction on Partner API To make a payment, you can use the PartnerApi facade:

ParameterRequiredDetails
PartnerTransIdYESUnique Id or reference for request
Type: Numeric
Max length 20.
CallbackURLYESa URI the API will send response payload to on completion of request
SecureHashYESAn HMACSHA256 hash of some of the parameters in the request.
Below are the required parameters and the order they should be concatenated.

_ServiceName + ServiceNumber+ SecondaryServiceNumber+Amount+Remarks+PartnerTransId_

key for hashing will be provided as part of your credentials.
ServiceNameYESService Name of the service being accessed. (Found in response of the services endpoint)
ServiceNumberYESThe account number / identifier of service.
Eg. Bank account Number, Mobile Money Wallet Number, Student ID.
SecondaryServiceNumberNORequired for specific Services that need extra data for payment.

Eg. Bank Transfer using GIP and NRT require this field.

NOTE

Bank Transfer (GIP) uses the GIP bank code as specified in the banks list ( to get banks list, use the services endpoint with banks as service name).

Bank Transfer (NRT) uses the bank sort codes provided by BOG. If GIP Code is provided for NRT transactions, the main sort code of the receiving bank is used.
BOG Sort Codes
AmountYESAmount to be paid
RemarksYESNarration/Remarks of transaction
PaidByYESName of Person making the transaction
PayeeNameNOName of Person receiving the transaction
$response = PartnerApi::pay( 
    '20220118000556', // PartnerTransId
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019', // CallbackURL
    'banktrf', // ServiceName
    '201113000110', // ServiceNumber
    '0.10', // Amount
    'credit account', // Remarks
    'Benedicta Ghansah', // PaidBy
    '300322', // SecondaryServiceNumber (optional)
    'Mary-Ann Edem Afeku' // PayeeName (optional)
);

Check Transaction Status

$response = PartnerApi::getStatus('202201140008');

Get List of Banks

To get a list of banks, you can use the PartnerApi facade:

$response = PartnerApi::getBanks('2022011800004');

Verify Account Details

Before making transfers, you can verify account details to ensure they exist and match expected names:

// Verify bank account details
$verification = PartnerApi::verifyBankAccount(
    '20220118000556', // PartnerTransId
    '0013014480189501', // Account Number
    '300312', // Bank Code
    'John Doe' // Expected Account Name (optional)
);

if ($verification['verified']) {
    echo "Account verified: " . $verification['account_name'];
} else {
    echo "Verification failed: " . $verification['message'];
}

Without Facade

You can also resolve the service from the container:

$partnerApi = app('partner-api');
// Or using the service container directly
$partnerApi = app(SerenityTechnologies\GtbGhana\PartnerApi\PartnerAPI::class);
// Then use any of the methods 
$response = $partnerApi->getServices('2022011800004');

Available Methods

MethodDescription
getServices(string $partnerTransId)Get a list of all available services
query(string $partnerTransId, string $serviceName, string $serviceNumber, string $secondaryServiceNumber = '')Query customer details for a specific service
pay(string $partnerTransId, string $callbackUrl, string $serviceName, string $serviceNumber, string $amount, string $remarks, string $paidBy, string $secondaryServiceNumber = '', string $payeeName = '')Make a payment for a service
getStatus(string $partnerTransId)Check the status of a transaction
getBanks(string $partnerTransId)Get a list of all supported banks
bankTransfer(string $partnerTransId, string $callbackUrl, string $accountNumber, string $amount, string $remarks, string $paidBy, string $bankCode = '', string $payeeName = '')Make a bank transfer
creditWallet(string $partnerTransId, string $callbackUrl, string $walletNumber, string $amount, string $remarks, string $paidBy, string $payeeName = '')Credit a wallet
debitWallet(string $partnerTransId, string $callbackUrl, string $walletNumber, string $amount, string $remarks, string $paidBy, string $payeeName = '')Debit a wallet
verifyBankAccount(string $partnerTransId, string $accountNumber, string $bankCode, string $expectedAccountName = null)Verify bank account details
verifyWallet(string $partnerTransId, string $walletNumber, string $expectedAccountName = null)Verify wallet details

PaymentResponse Methods

When working with verified transactions, the PaymentResponse object includes additional methods for accessing verification data:

MethodDescription
wasVerified()Check if verification was performed
isVerified()Check if verification was successful
getVerificationData()Get the complete verification data array

Wallet Services Details

Make a Bank Transfer

Bank Transfer (banktrf)

  • Used to transfer funds to a bank account
  • Requires a bank code for the recipient's bank when using GIP
  • Supports both GIP and NRT transfer methods

To make a bank transfer, you can use the bankTransfer method:

// Standard bank transfer
$response = PartnerApi::bankTransfer(
    '20220118000556', // PartnerTransId
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019', // CallbackURL
    '201113000110', // Account Number
    '0.10', // Amount
    'credit account', // Remarks
    'Benedicta Ghansah', // PaidBy
    '300322', // Bank Code (optional)
    'Mary-Ann Edem Afeku' // PayeeName (optional)
);

// Verified bank transfer (automatically verifies account before transfer)
$response = PartnerApi::bankTransfer(
    '20220118000556', // PartnerTransId
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019', // CallbackURL
    '201113000110', // Account Number
    '0.10', // Amount
    'credit account', // Remarks
    'Benedicta Ghansah', // PaidBy
    '300322', // Bank Code (required for verification)
    'Mary-Ann Edem Afeku', // PayeeName (optional, used for name verification)
    true // Verify account before transfer
);

// Access verification data from the response
if ($response->wasVerified()) { 
    if ($response->isVerified()) { 
    echo "Account verified: " . $response->getVerificationData()['account_name'];
     } else { 
     echo "Verification failed: " . $response->getVerificationData()['message']; } 
  }

Credit a Wallet

Credit Wallet (CREDITWALLET)

  • Used to add funds to a mobile money wallet
  • Typically used for customer refunds or top-ups To credit a wallet, you can use the creditWallet method:
// Standard wallet credit
$response = PartnerApi::creditWallet(
    '20220118000557', // PartnerTransId
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019', // CallbackURL
    '0241234567', // Wallet Number
    '50.00', // Amount
    'wallet credit', // Remarks
    'John Doe', // PaidBy
    'Jane Doe' // PayeeName (optional)
);

// Verified wallet credit (automatically verifies wallet before crediting)
$response = PartnerApi::creditWallet(
    '20220118000557', // PartnerTransId
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019', // CallbackURL
    '0241234567', // Wallet Number
    '50.00', // Amount
    'wallet credit', // Remarks
    'John Doe', // PaidBy
    'Jane Doe', // PayeeName (optional, used for name verification)
    true // Verify wallet before crediting
);

Debit a Wallet

Debit Wallet (DEBITWALLET)

  • Used to deduct funds from a mobile money wallet
  • Typically used for payments or withdrawals

To debit a wallet, you can use the debitWallet method:

// Standard wallet debit
$response = PartnerApi::debitWallet(
    '20220118000558', // PartnerTransId
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019', // CallbackURL
    '0241234567', // Wallet Number
    '25.00', // Amount
    'wallet debit', // Remarks
    'John Doe', // PaidBy
    'Jane Doe' // PayeeName (optional)
);

// Verified wallet debit (automatically verifies wallet before debiting)
$response = PartnerApi::debitWallet(
    '20220118000558', // PartnerTransId
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019', // CallbackURL
    '0241234567', // Wallet Number
    '25.00', // Amount
    'wallet debit', // Remarks
    'John Doe', // PaidBy
    'Jane Doe', // PayeeName (optional, used for name verification)
    true // Verify wallet before debiting
);

Working with Responses

All methods return response objects that provide helpful methods for working with the API responses:

// Get services
$response = PartnerApi::getServices('2022011800004');

// Check if request was successful
if ($response->isSuccessful()) {
    // Get the services data
    $services = $response->getServices();
    
    // Find a specific service
    $bankTransferService = $response->getServiceByName('banktrf');
}

// Make a payment
$paymentResponse = PartnerApi::bankTransfer(
    '20220118000556',
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019',
    '201113000110',
    '0.10',
    'credit account',
    'Benedicta Ghansah',
    '300322'
);

// Check payment status
if ($paymentResponse->isSuccessful()) {
    echo "Transaction completed successfully";
} elseif ($paymentResponse->isPending()) {
    echo "Transaction is pending";
} elseif ($paymentResponse->isFailed()) {
    echo "Transaction failed: " . $paymentResponse->getMessage();
}

// Get transaction reference
$transactionRef = $paymentResponse->getTransactionReference();

Error Handling

The package will throw an Illuminate\Http\Client\ConnectionException if there are network issues or if the API is unreachable. You should wrap your calls in try-catch blocks:

use Illuminate\Http\Client\ConnectionException; 
use SerenityTechnologies\GtbGhana\PartnerApi\Facades\PartnerApi;

try { 
    $response = PartnerApi::getServices('2022011800004');
} catch (ConnectionException $e) { 
    // Handle connection error 
    Log::error('Failed to connect to GTB Partner API: ' . $e->getMessage()); 
}

For verification errors, the package will throw exceptions when using automatic verification:

use SerenityTechnologies\GtbGhana\PartnerApi\Facades\PartnerApi;

try {
    // This will throw an exception if verification fails
    $response = PartnerApi::bankTransfer(
        '20220118000556',
        'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019',
        '201113000110',
        '0.10',
        'credit account',
        'Benedicta Ghansah',
        '300322',
        'Expected Name',
        true // Enable verification
    );
} catch (Exception $e) {
    echo "Transfer failed: " . $e->getMessage();
}

Available Services

Based on the API documentation, the following services are available:

  1. Airtime

    • Service Name: airtime
    • Description: Airtime Topup
  2. Utilities

    • Service Name: dstv - DSTV
    • Service Name: gotv - GOTV
    • Service Name: gwcl - Ghana Water Payment
    • Service Name: ecg - ECG Postpaid Payment
    • Service Name: box_office - BOX OFFICE
  3. Investment/Pensions

    • Service Name: mfund - DATABANK MFUND
    • Service Name: epack - DATABANK EPACK
    • Service Name: bfund - DATABANK BFUND
  4. Education

    • Service Name: sltf - STUDENT LOAN TRUST FUND
  5. Transfers

    • Service Name: banktrf - Bank Transfer
    • Service Name: CREDITWALLET - Credit Wallet
    • Service Name: DEBITWALLET - Debit Wallet

Account Verification

The package includes robust account verification features to help prevent sending money to incorrect accounts:

Name Matching

The verification system includes intelligent name matching that can handle:

  • Case differences
  • Extra whitespace
  • Name abbreviations
  • Minor spelling variations
  • Partial name matches

Verification Process

  1. Account/Wallet Lookup: The system first queries the API to verify the account exists
  2. Name Comparison: If an expected name is provided, it's compared with the returned name
  3. Match Analysis: The system uses multiple techniques to determine if names match:

    • Direct comparison
    • Partial matching
    • Word-by-word comparison
    • Levenshtein distance for minor character differences

Manual Verification

You can manually verify accounts before making transfers:

// Verify a bank account
$verification = PartnerApi::verifyBankAccount(
    '20220118000556',     // PartnerTransId
    '0013014480189501',   // Account Number
    '300312',             // Bank Code
    'John Doe'            // Expected Account Name
);

if ($verification['verified']) {
    // Account is verified, proceed with transfer
    $transfer = PartnerApi::bankTransfer(/* parameters */);
} else {
    // Handle verification failure
    echo "Verification failed: " . $verification['message'];
}

Automatic Verification

You can enable automatic verification during transfers by setting the verify parameter to true:

// This will automatically verify before transferring
$response = PartnerApi::bankTransfer(
    '20220118000556',
    'https://webhook.site/4c4d2ec6-542f-45fe-b555-6bfbc51fd019',
    '201113000110',
    '0.10',
    'credit account',
    'Benedicta Ghansah',
    '300322',           // Required for verification
    'Expected Name',    // Used for name verification
    true                // Enable automatic verification
);

Caching

This package now includes caching mechanisms to reduce the number of calls to the GTBank API, improving performance significantly.

Configuration

You can configure the cache TTL in your partner-api.php config file:

'cache_ttl' => 30 // Cache TTL in minutes

Cached Methods

  • verifyBankAccount() - Caches bank account verification results
  • verifyWallet() - Caches wallet verification results
  • verifyAccount() in BankTransferService - Caches bank transfer verification results
  • Wallet verification in CreditWalletService and DebitWalletService

Cache Management

You can clear specific caches using these methods:

  • clearBankVerificationCache(string $accountNumber, string $bankCode)
  • clearWalletVerificationCache(string $serviceName, string $walletNumber)
  • clearBankVerificationResultCache(string $accountNumber, string $bankCode, ?string $expectedAccountName = null)
  • clearWalletVerificationResultCache(string $serviceName, string $walletNumber, ?string $expectedAccountName = null)

To force a refresh of cached data, use the forceRefresh parameter in verification methods.

Webhook Handling (New Event-Based Approach with Signature Validation)

⚠️ DEPRECATION NOTICE: The old callback handler classes (CallbackManager, BaseCallbackHandler, PaymentCallbackHandler, etc.) and the deprecated CallbackController are deprecated as of v2.0.0. Please migrate to the new event-based webhook system described below. The old classes will be removed in a future version.

The package now provides a comprehensive webhook handling system with:

  • Automatic signature validation via middleware
  • Payload validation for required fields and proper structure
  • Event dispatching for flexible handling in your application
  • Service-specific endpoints for different transaction types

Quick Setup

1. Configure your webhook URL in GTB dashboard:

https://yourdomain.com/gtb-webhook

2. Add to your .env:

GTB_PARTNER_SECRET_KEY=your_secret_key_here
GTB_WEBHOOK_VERIFY_SIGNATURE=true
GTB_WEBHOOK_VALIDATE_PAYLOAD=true

# Optional: Enable status verification with GTB API
GTB_WEBHOOK_STATUS_VERIFY=false

3. Register event listeners in EventServiceProvider:

use Illuminate\Support\Facades\Event;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackProcessed;

public function boot(): void
{
    Event::listen(CallbackProcessed::class, function (CallbackProcessed $event) {
        if ($event->wasSuccessful()) {
            // Update your database
            // Send notifications
            // Update balances
        }
    });
}

That's it! The package handles signature validation, payload validation, and event dispatching automatically.

Webhook Routes

The package automatically registers these routes:

RouteMethodDescription
/gtb-webhookPOSTMain webhook endpoint (handles all types)
/gtb-webhook/bank-transferPOSTBank transfer specific webhook
/gtb-webhook/walletPOSTWallet (credit/debit) specific webhook

To customize routes, publish them:

php artisan vendor:publish --provider="SerenityTechnologies\GtbGhana\PartnerApi\PartnerApiServiceProvider" --tag="routes"

Middleware

All webhook routes use middleware for validation:

  1. ValidateWebhookPayload - Validates required fields and structure
  2. ValidateWebhookSignature - Validates HMAC-SHA256 signature

Signature verification is automatically disabled in testing environments.

Configuration

Publish the config file:

php artisan vendor:publish --provider="SerenityTechnologies\GtbGhana\PartnerApi\PartnerApiServiceProvider" --tag="config"

Key webhook settings in config/partner-api.php:

'webhooks' => [
    'verify_signature' => env('GTB_WEBHOOK_VERIFY_SIGNATURE', true),
    'validate_payload' => env('GTB_WEBHOOK_VALIDATE_PAYLOAD', true),
    'logging' => env('GTB_WEBHOOK_LOGGING', true),
    'route_prefix' => env('GTB_WEBHOOK_ROUTE_PREFIX', 'gtb-webhook'),
    'middleware' => env('GTB_WEBHOOK_MIDDLEWARE', []),
],

Available Events

Generic Events:

  • CallbackReceived - Dispatched when any webhook is received (includes validation status)
  • CallbackProcessed - Dispatched when a webhook is successfully processed
  • CallbackFailed - Dispatched when webhook processing fails

Service-Specific Events:

  • BankTransferCallbackReceived - Dispatched for bank transfer webhooks
  • BankTransferCallbackProcessed - Dispatched when bank transfer is processed
  • WalletCallbackReceived - Dispatched for wallet (credit/debit) webhooks
  • WalletCallbackProcessed - Dispatched when wallet transaction is processed
  • PaymentCallbackReceived - Dispatched for generic payment webhooks
  • PaymentCallbackProcessed - Dispatched when generic payment is processed

Event Properties

// CallbackReceived event
$event->payload              // array - Full webhook payload
$event->serviceType          // string - 'banktrf', 'wallet', etc.
$event->signatureVerified    // bool - Whether signature was verified
$event->payloadValidated     // bool - Whether payload was validated
$event->getTransactionId()   // string|null - GTB transaction ID
$event->getPartnerTransId()  // string|null - Your transaction ID
$event->getStatus()          // string|null - Transaction status
$event->isValidated()        // bool - Both signature and payload validated

// CallbackProcessed event
$event->payload              // array - Full webhook payload
$event->result               // array - Processing result
$event->serviceType          // string - Service type
$event->wasSuccessful()      // bool - Processing success
$event->getTransactionId()   // string|null - GTB transaction ID
$event->getPartnerTransId()  // string|null - Your transaction ID

Setting Up Event Listeners

Option 1: Inline Registration (Recommended for Simple Cases)

In your EventServiceProvider or any service provider:

use Illuminate\Support\Facades\Event;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackReceived;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackProcessed;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackFailed;

public function boot(): void
{
    // Handle all callbacks
    Event::listen(CallbackReceived::class, function (CallbackReceived $event) {
        \Log::info('Callback received', [
            'partner_trans_id' => $event->getPartnerTransId(),
            'gtb_trans_id' => $event->getTransactionId(),
            'status' => $event->getStatus(),
            'service_type' => $event->serviceType,
            'signature_verified' => $event->isSignatureVerified(),
            'payload_validated' => $event->isPayloadValidated(),
        ]);
        
        // Your business logic here
    });

    // Handle successfully processed callbacks
    Event::listen(CallbackProcessed::class, function (CallbackProcessed $event) {
        if ($event->wasSuccessful()) {
            // Update your database
            // Send notifications
            // Update balances
        }
    });

    // Handle failed callbacks
    Event::listen(CallbackFailed::class, function (CallbackFailed $event) {
        \Log::error('Callback failed', [
            'error' => $event->errorMessage,
            'partner_trans_id' => $event->getPartnerTransId(),
        ]);
        
        // Alert administrators
        // Retry logic
    });
}

Option 2: Service-Specific Listeners

For more granular control, listen to service-specific events:

use Illuminate\Support\Facades\Event;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\BankTransferCallbackReceived;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\WalletCallbackReceived;

public function boot(): void
{
    // Handle bank transfer callbacks
    Event::listen(BankTransferCallbackReceived::class, function (BankTransferCallbackReceived $event) {
        $accountNumber = $event->getAccountNumber();
        $bankCode = $event->getBankCode();
        $status = $event->getStatus();
        
        // Update bank transfer in your database
        // Send notifications to recipient
    });

    // Handle wallet callbacks
    Event::listen(WalletCallbackReceived::class, function (WalletCallbackReceived $event) {
        if ($event->isCreditWallet()) {
            // Handle credit wallet
        } elseif ($event->isDebitWallet()) {
            // Handle debit wallet
        }
        
        $walletNumber = $event->getWalletNumber();
        $network = $event->getNetwork();
    });
}

Option 3: Listener Classes (Recommended for Complex Logic)

Create listener classes for better organization:

// In app/Listeners/HandlePaymentCallback.php
namespace App\Listeners;

use SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackProcessed;

class HandlePaymentCallback
{
    public function handle(CallbackProcessed $event): void
    {
        // Access callback data
        $payload = $event->payload;
        $result = $event->result;
        $serviceType = $event->serviceType;
        
        // Your business logic
        if ($event->wasSuccessful()) {
            // Update transaction status
            // Send notifications
            // Update user balances
        }
    }
}

Register in EventServiceProvider:

protected $listen = [
    \SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackReceived::class => [
        \App\Listeners\HandlePaymentCallback::class,
    ],
    \SerenityTechnologies\GtbGhana\PartnerApi\Events\BankTransferCallbackReceived::class => [
        \App\Listeners\HandleBankTransferCallback::class,
    ],
    \SerenityTechnologies\GtbGhana\PartnerApi\Events\WalletCallbackReceived::class => [
        \App\Listeners\HandleWalletCallback::class,
    ],
];

Setting up Callback Routes

Even with the event-based system, you still need to set up routes to receive callbacks:

// In routes/web.php or routes/api.php
use SerenityTechnologies\GtbGhana\PartnerApi\Http\Controllers\CallbackController;

// Generic callback endpoint (recommended)
Route::post('/gtb-api/callback', [CallbackController::class, 'handleCallback']);

// Service-specific endpoints (optional, deprecated but still supported)
Route::post('/gtb-api/callback/bank-transfer', [CallbackController::class, 'handleBankTransferCallback']);
Route::post('/gtb-api/callback/wallet', [CallbackController::class, 'handleWalletCallback']);

Example: Complete Implementation

Here's a complete example of handling callbacks in a production application:

// In app/Providers/EventServiceProvider.php
namespace App\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackProcessed;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\BankTransferCallbackProcessed;
use SerenityTechnologies\GtbGhana\PartnerApi\Events\WalletCallbackProcessed;
use App\Models\Transaction;
use App\Services\NotificationService;

class EventServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Handle successful payment callbacks
        Event::listen(CallbackProcessed::class, function (CallbackProcessed $event) {
            if (!$event->wasSuccessful()) {
                return;
            }

            $partnerTransId = $event->getPartnerTransId();
            $transaction = Transaction::where('partner_trans_id', $partnerTransId)->first();

            if ($transaction) {
                $transaction->update([
                    'status' => strtolower($event->payload['Status']),
                    'gtb_trans_id' => $event->getTransactionId(),
                    'completed_at' => now(),
                ]);

                // Send notification to user
                app(NotificationService::class)->sendTransactionNotification(
                    $transaction->user,
                    $transaction
                );
            }
        });

        // Handle bank transfer callbacks
        Event::listen(BankTransferCallbackProcessed::class, function (BankTransferCallbackProcessed $event) {
            if (!$event->wasSuccessful()) {
                return;
            }

            // Update bank transfer specific data
            $accountNumber = $event->payload['Data']['ServiceNumber'] ?? null;
            $bankCode = $event->payload['Data']['SecondaryServiceNumber'] ?? null;
            
            \Log::info('Bank transfer completed', [
                'account' => $accountNumber,
                'bank_code' => $bankCode,
            ]);
        });

        // Handle wallet callbacks
        Event::listen(WalletCallbackProcessed::class, function (WalletCallbackProcessed $event) {
            if (!$event->wasSuccessful()) {
                return;
            }

            // Update wallet transaction
            $walletNumber = $event->payload['Data']['ServiceNumber'] ?? null;
            $network = $event->payload['Data']['Network'] ?? null;
            
            \Log::info('Wallet transaction completed', [
                'wallet' => $walletNumber,
                'network' => $network,
                'type' => $event->walletType,
            ]);
        });
    }
}

Migrating from the Old System

If you're using the deprecated callback handlers, here's how to migrate:

Before (Old System):

class CustomBankTransferHandler extends BankTransferCallbackHandler
{
    protected function handleCompletedPayment(PaymentResponse $response): array
    {
        $result = parent::handleCompletedPayment($response);
        
        // Custom logic
        $this->updateDatabase($response);
        $this->sendNotification($response);
        
        return $result;
    }
}

After (New Event System):

Event::listen(BankTransferCallbackProcessed::class, function (BankTransferCallbackProcessed $event) {
    if (!$event->wasSuccessful()) {
        return;
    }
    
    // Your custom logic
    $this->updateDatabase($event->payload);
    $this->sendNotification($event->payload);
});

Callback Payload

The callback payload from GTB API typically looks like this:

{
   "SecureHash": "47ff0d9b1e7bbdaa97ba3441e76d0a308d7d23aba38882a3277bc4bb0bc30585",
   "GTBTransId": "1075202201140008",
   "PartnerTransId": "202201140008",
   "ProviderTransId": "",
   "Status": "COMPLETED",
   "Message": "Transaction Successful",
   "Data": null
}

Testing Callbacks

You can test your event listeners by dispatching test events:

use SerenityTechnologies\GtbGhana\PartnerApi\Events\CallbackReceived;

// In a test or tinker session
Event::dispatch(new CallbackReceived([
    'GTBTransId' => '12345',
    'PartnerTransId' => '67890',
    'Status' => 'COMPLETED',
    'Message' => 'Test transaction',
], 'banktrf'));

Or send test HTTP requests:

curl -X POST https://yourdomain.com/gtb-api/callback \
  -H "Content-Type: application/json" \
  -d '{
    "SecureHash": "test-hash",
    "GTBTransId": "12345",
    "PartnerTransId": "67890",
    "Status": "COMPLETED",
    "Message": "Test transaction"
  }'

Testing

To run the tests:

  composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The GNU AFFERO GENERAL PUBLIC LICENSE. Please see License File for more information.