matt-di/laravel-telebirr

Telebirr payment gateway integration for Laravel applications

Maintainers

Package info

github.com/Matt-di/laravel-telebirr

pkg:composer/matt-di/laravel-telebirr

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

v1.0.0 2025-11-14 18:16 UTC

This package is not auto-updated.

Last update: 2026-05-20 17:25:31 UTC


README

Latest Version on Packagist Total Downloads License

Telebirr payment gateway integration for Laravel applications with support for single and multi-merchant setups.

🎯 Integration Type

This package is designed for Telebirr Super App integration where the frontend handles payment requests through the Telebirr mobile application.

  • Backend Role: Generates signed payment requests and handles webhooks
  • Frontend Role: Uses Telebirr mobile SDK to process payments
  • 📖 Frontend Integration: Refer to Telebirr Developer Documentation for mobile app integration details
  • 🔗 Official Resources: Check Telebirr's official documentation for SDK implementation and best practices

Features

  • 🚀 Simple Setup - Get started in minutes with zero configuration
  • 🏪 Multi-Merchant Support - Perfect for multi-branch or multi-store applications
  • 🔐 Enterprise Security - RSA PSS signatures, webhook verification, SSL controls
  • High Performance - Token caching, queue-based processing, retry logic
  • 🛠 Developer Friendly - Artisan commands, comprehensive logging, extensive configuration
  • 🔧 Highly Configurable - Extensive customization options for any use case
  • 📱 Mobile SDK Ready - Raw request generation for Telebirr mobile applications
  • 🎯 Event Driven - Laravel events for payment lifecycle hooks

Installation

Basic Setup (Single Merchant)

composer require matt-di/laravel-telebirr
php artisan telebirr:install

Multi-Merchant Setup

composer require Matt-di/laravel-telebirr
php artisan telebirr:install --mode=multi --run-migrations

Quick Start

1. Configure Environment

Add your Telebirr credentials to .env:

# Single merchant mode
TELEBIRR_MODE=single
TELEBIRR_FABRIC_APP_ID=your_fabric_app_id
TELEBIRR_MERCHANT_APP_ID=your_merchant_app_id
TELEBIRR_MERCHANT_CODE=123456
TELEBIRR_APP_SECRET=your_app_secret
TELEBIRR_RSA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"

2. Create Payment Request

use Telebirr\LaravelTelebirr\Facades\Telebirr;

$rawRequest = Telebirr::initiatePayment([
    'txn_ref' => 'TXN_' . time(),
    'amount' => 150.00,
    'subject' => 'Order Payment'
]);

// Returns: "appid=MERCHANT123&merch_code=MC456&nonce_str=abc...&prepay_id=PRE123...&timestamp=202512021200&sign_type=SHA256WithRSA&sign=signed_data..."

3. Handle Webhook (Automatically Done)

The package handles webhook verification, signature checking, and payment verification automatically.

Usage Examples

Single Merchant

Step 1: Configure Environment

Add your Telebirr credentials to .env:

TELEBIRR_MODE=single
TELEBIRR_FABRIC_APP_ID=your_fabric_app_id
TELEBIRR_MERCHANT_APP_ID=your_merchant_app_id
TELEBIRR_MERCHANT_CODE=your_merchant_code
TELEBIRR_APP_SECRET=your_app_secret
TELEBIRR_RSA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----"

Step 2: Install the Package

php artisan telebirr:install

Step 3: Initiate a Payment

use Telebirr\LaravelTelebirr\Facades\Telebirr;

$rawRequest = Telebirr::initiatePayment([
    'txn_ref' => 'TXN_' . time(),
    'amount' => 150.00,
    'subject' => 'Order Payment'
]);

// Returns a signed query string for the mobile SDK:
// "appid=MERCHANT123&merch_code=MC456&nonce_str=abc...&prepay_id=PRE123...&timestamp=202512021200&sign_type=SHA256WithRSA&sign=signed_data..."

Step 4: Web Checkout (Browser-based Payment)

For web-based checkout, the package provides a dedicated API endpoint that handles the entire flow:

Using the API endpoint (recommended):

// POST /api/telebirr/web-checkout
// Request:
{
    "invoice_id": "INV_12345",
    "amount": 150.00,
    "subject": "Order Payment"
}

// Response:
{
    "success": true,
    "data": {
        "invoice_id": "INV_12345",
        "prepay_id": "PRE123456789",
        "checkout_url": "https://developerportal.ethiotelebirr.et:38443/payment/web/paygate?appid=...&prepay_id=...&sign=...",
        "amount": 150.00,
        "subject": "Order Payment"
    }
}

Then redirect the user to the checkout URL:

// Frontend example
fetch("/api/telebirr/web-checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    invoice_id: "INV_12345",
    amount: 150.0,
    subject: "Order Payment",
  }),
})
  .then((res) => res.json())
  .then((data) => {
    if (data.success) {
      window.location.href = data.data.checkout_url;
    }
  });

Using the Facade directly:

use Telebirr\LaravelTelebirr\Facades\Telebirr;

// First, initiate payment to get a prepay_id
$rawRequest = Telebirr::initiatePayment([
    'txn_ref' => 'TXN_' . time(),
    'amount' => 150.00,
    'subject' => 'Order Payment'
]);

// Parse the prepay_id from the raw request
parse_str($rawRequest, $params);
$prepayId = $params['prepay_id'];

// Generate the checkout URL
$checkoutUrl = Telebirr::generateCheckoutUrl($prepayId);

// Redirect user to the checkout URL
return redirect($checkoutUrl);

#### USSD Push Payment (C2B)

```php
use Telebirr\LaravelTelebirr\Facades\Telebirr;

// Initiate USSD push payment
$rawRequest = Telebirr::ussdPush([
    'phone'   => '0912345678', // or international format +2519...
    'txn_ref' => 'TXN_' . time(),
    'amount'  => 150.00,
    'subject' => 'Order Payment',
    // Optional overrides
    // 'trade_type' => 'Checkout',
    // 'timeout_express' => '30m',
]);

// $rawRequest is a signed query string that can be sent via USSD or SMS gateway

Custom Parameter Overrides

You can override any request parameter by passing additional keys in the order data. For example:

$rawRequest = Telebirr::initiatePayment([
    'txn_ref' => 'TXN_' . time(),
    'amount'  => 200.00,
    'subject' => 'Custom Order',
    // Override trade type and timeout
    'trade_type' => 'Purchase',
    'timeout_express' => '45m',
]);

All other default parameters (like appid, merch_code, nonce_str) will be generated automatically.

The checkout URL is configured via environment variables:

```env
TELEBIRR_WEB_BASE_URL=https://developerportal.ethiotelebirr.et:38443/payment/web/paygate?
TELEBIRR_WEB_VERSION=1.0
TELEBIRR_WEB_TRADE_TYPE=Checkout

Step 5: Handle Webhook

The package automatically handles incoming webhooks. Configure your webhook URL:

TELEBIRR_WEBHOOK_URL=https://your-domain.com/api/telebirr/webhook

Test your webhook:

php artisan telebirr:setup-webhook --test

Multi-Merchant (Branch-Based)

// Payment for specific branch
Telebirr::initiatePayment([
    'txn_ref' => 'TXN_123',
    'amount' => 100.00,
    'subject' => 'Branch Order'
], ['branch_id' => 1]);

Custom Context

// Organized by stores
Telebirr::initiatePayment($data, ['store_id' => 5]);

// Or even custom types
Telebirr::initiatePayment($data, ['owner_type' => 'restaurant', 'owner_id' => 10]);

Payment Verification

$result = Telebirr::verifyPayment('TXN_123');
if ($result && $result['order_status'] === 'PAY_SUCCESS') {
    // Payment successful
}

Auth Token Retrieval

$userInfo = Telebirr::getAuthToken($accessToken);

Error Handling

try {
    $paymentRequest = Telebirr::initiatePayment([
        'txn_ref' => 'TXN_' . time(),
        'amount' => 150.00,
        'subject' => 'Order Payment',
    ]);

    // Process successful payment initiation

} catch (\Telebirr\LaravelTelebirr\Exceptions\TelebirrException $e) {
    // Handle payment initiation errors
    Log::error('Telebirr payment failed: ' . $e->getMessage());

    // Show user-friendly error message
    return back()->withErrors(['payment' => 'Payment initiation failed. Please try again.']);
}

Event Listeners

Listen to payment lifecycle events:

Event::listen(Telebirr\LaravelTelebirr\Events\PaymentInitiated::class, function ($event) {
    // Payment initiated
});

Event::listen(Telebirr\LaravelTelebirr\Events\PaymentVerified::class, function ($event) {
    // Payment verified and successful
    // Update your order/invoice status here
});

Event::listen(Telebirr\LaravelTelebirr\Events\WebhookReceived::class, function ($event) {
    // Raw webhook received
});

Testing

Test your integration:

# Test API connectivity
php artisan telebirr:test-connection

# Setup and test webhooks
php artisan telebirr:setup-webhook --test

Configuration

Single Merchant Mode

  • Uses global ENV configuration
  • No database required
  • Perfect for simple applications

Multi-Merchant Mode

  • Database-driven merchant management
  • Configurable relationship mappings
  • Enterprise-ready for complex applications

API Reference

Facade Methods

Telebirr::initiatePayment(array $data, array $context = [])
Telebirr::verifyPayment(string $transactionId, array $context = [])
Telebirr::queryOrder(string $orderId, array $context = [])
Telebirr::getAuthToken(string $accessToken, array $context = [])
Telebirr::handleWebhook(Request $request)

HTTP Endpoints

  • POST /api/telebirr/order - Create payment order
  • POST /api/telebirr/verify - Verify payment status
  • POST /api/telebirr/query - Query order details
  • POST /api/telebirr/auth - Get auth token
  • POST /api/telebirr/webhook - Webhook handler

Advanced Configuration

Publish config for full customization:

php artisan vendor:publish --tag=telebirr-config

Custom Merchant Resolver

// config/telebirr.php
'merchant' => [
    'resolver' => App\Services\CustomMerchantResolver::class,
],

Custom Webhook Handler

'webhook' => [
    'handler' => App\Services\CustomWebhookHandler::class,
],

Migration Guide

From Single to Multi-Merchant

  1. Change mode: TELEBIRR_MODE=multi
  2. Run: php artisan telebirr:install --mode=multi --run-migrations
  3. Add merchant records to telebirr_merchants table
  4. Update code to pass merchant context where needed

Complete Integration Example

Here's a complete real-world integration example:

<?php

namespace App\Http\Controllers;

use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Telebirr\LaravelTelebirr\Facades\Telebirr;
use Telebirr\LaravelTelebirr\Exceptions\TelebirrException;

class PaymentController extends Controller
{
    /**
     * Create payment order and initiate Telebirr payment
     */
    public function checkout(Request $request)
    {
        $request->validate([
            'amount' => 'required|numeric|min:1|max:10000',
            'description' => 'required|string|max:255',
        ]);

        // 1. Create order in your system first
        $order = Order::create([
            'user_id' => auth()->id(),
            'amount' => $request->amount,
            'currency' => 'ETB',
            'description' => $request->description,
            'status' => 'pending',
            'reference' => 'ORD_' . time() . '_' . auth()->id(),
        ]);

        try {
            // 2. Initiate Telebirr payment
            $paymentRequest = Telebirr::initiatePayment([
                'txn_ref' => $order->reference,
                'amount' => $order->amount,
                'subject' => 'Order #' . $order->id,
                'description' => $order->description,
                'notify_url' => route('webhook.telebirr'),
                'return_url' => route('payment.success', $order->id),
                'timeout_express' => '30m',
            ]);

            // 3. Update order with payment request details
            $order->update([
                'payment_raw_request' => $paymentRequest,
                'raw_request_parsed' => $this->parseRawRequest($paymentRequest),
            ]);

            Log::info('Telebirr payment initiated', [
                'order_id' => $order->id,
                'amount' => $order->amount,
                'payment_ref' => $order->reference
            ]);

            return response()->json([
                'success' => true,
                'payment_raw_request' => $paymentRequest,
                'order_id' => $order->id,
                'reference' => $order->reference
            ]);

        } catch (TelebirrException $e) {
            // 4. Handle payment initiation failure
            $order->update(['status' => 'failed']);

            Log::error('Telebirr payment initiation failed', [
                'order_id' => $order->id,
                'error' => $e->getMessage()
            ]);

            return response()->json([
                'success' => false,
                'message' => 'Payment initiation failed. Please try again.',
                'error' => $e->getMessage()
            ], 422);
        }
    }

    /**
     * Handle Telebirr webhooks
     */
    public function webhook(Request $request)
    {
        try {
            $payload = $request->all();

            // Verify webhook and process payment
            $result = Telebirr::handleWebhook($request);

            if ($result && isset($payload['merch_order_id'])) {
                // Update order status based on payment result
                $order = Order::where('reference', $payload['merch_order_id'])->first();

                if ($order) {
                    if ($payload['trade_status'] === 'Completed') {
                        $order->update([
                            'status' => 'paid',
                            'paid_at' => now(),
                            'payment_details' => $payload
                        ]);

                        Log::info('Order marked as paid', [
                            'order_id' => $order->id,
                            'payment_ref' => $payload['merch_order_id']
                        ]);

                    } elseif ($payload['trade_status'] === 'Failed') {
                        $order->update([
                            'status' => 'failed',
                            'payment_details' => $payload
                        ]);

                        Log::warning('Payment failed', [
                            'order_id' => $order->id,
                            'payment_ref' => $payload['merch_order_id']
                        ]);
                    }
                }
            }

            return response()->json(['code' => 0, 'message' => 'Success']);

        } catch (\Exception $e) {
            Log::error('Webhook processing failed', [
                'error' => $e->getMessage(),
                'payload' => $request->all()
            ]);

            return response()->json(['code' => 1, 'message' => 'Processing failed'], 500);
        }
    }

    /**
     * Payment success page
     */
    public function success(Order $order)
    {
        // Verify final payment status
        try {
            $result = Telebirr::verifyPayment($order->reference);

            if ($result && $result['order_status'] === 'PAY_SUCCESS') {
                $order->update([
                    'status' => 'paid',
                    'verified_at' => now()
                ]);

                return view('payment.success', compact('order'));
            } else {
                return view('payment.pending', compact('order'));
            }

        } catch (TelebirrException $e) {
            Log::error('Payment verification failed', [
                'order_id' => $order->id,
                'error' => $e->getMessage()
            ]);

            return view('payment.error', [
                'order' => $order,
                'error' => 'Unable to verify payment status'
            ]);
        }
    }
}

Troubleshooting

Common Issues

RSA Key Format Error

# Ensure your private key includes proper PEM headers
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7VITN...
-----END PRIVATE KEY-----

Network Timeouts

# Increase timeout in config or .env
TELEBIRR_API_TIMEOUT=30

Webhook Signature Verification Failures

# Test webhook locally first
php artisan telebirr:setup-webhook --url=https://your-domain.com/api/telebirr/webhook --test

# Check your public/private key pair
php artisan telebirr:test-connection

Multi-Merchant Configuration Issues

# Ensure merchant records exist
php artisan tinker
>>> App\Models\TelebirrMerchant::count()
>>> App\Models\TelebirrMerchant::first()

# Test with merchant context
Telebirr::initiatePayment($data, ['store_id' => 1])

Database Connection Issues

# Run migrations for multi-merchant mode
php artisan migrate
php artisan telebirr:install --run-migrations

Queue Configuration for Background Processing

# Ensure queue is configured for payment verification
QUEUE_CONNECTION=database
TELEBIRR_QUEUE_VERIFY_PAYMENT_ENABLED=true

Debug Mode

Enable detailed logging for troubleshooting:

LOG_LEVEL=debug
TELEBIRR_LOGGING_ENABLED=true

Getting Help

  • Check logs: storage/logs/laravel.log
  • Review network requests in browser dev tools
  • Test API credentials manually with Postman
  • Verify webhook endpoint is publicly accessible

Security

  • RSA PSS signature validation
  • Webhook signature verification with timestamp tolerance
  • SSL certificate validation (configurable)
  • Comprehensive audit logging
  • Configurable sensitive data masking

Performance

  • Fabric token caching (55-minute TTL)
  • Queue-based payment verification
  • Retry logic with exponential backoff
  • Database connection pooling aware
  • Optimized queries with proper indexing

Requirements

  • PHP 8.1+
  • Laravel 9.0+
  • phpseclib 3.0+

Contributing

Please see CONTRIBUTING for details.

License

This package is open-sourced software licensed under the MIT license.

Support