matt-di / laravel-telebirr
Telebirr payment gateway integration for Laravel applications
Requires
- php: ^8.1
- illuminate/cache: ^9.0|^10.0|^11.0
- illuminate/events: ^9.0|^10.0|^11.0
- illuminate/http: ^9.0|^10.0|^11.0
- illuminate/queue: ^9.0|^10.0|^11.0
- illuminate/support: ^9.0|^10.0|^11.0
- laravel/framework: ^9.0|^10.0|^11.0
- phpseclib/phpseclib: ^3.0
Requires (Dev)
- mockery/mockery: ^1.4
- orchestra/testbench: ^7.0|^8.0|^9.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^9.0|^10.0
This package is not auto-updated.
Last update: 2026-05-20 17:25:31 UTC
README
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...×tamp=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...×tamp=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 orderPOST /api/telebirr/verify- Verify payment statusPOST /api/telebirr/query- Query order detailsPOST /api/telebirr/auth- Get auth tokenPOST /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
- Change mode:
TELEBIRR_MODE=multi - Run:
php artisan telebirr:install --mode=multi --run-migrations - Add merchant records to
telebirr_merchantstable - 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
- 📖 Documentation: https://laravel-telebirr.com
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions