almesery / laravel-geidea
Geidea Payment Gateway integration for Laravel
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/almesery/laravel-geidea
Requires
- php: ^8.1|^8.2|^8.3
- guzzlehttp/guzzle: ^7.0
- illuminate/http: ^10.0|^11.0
- illuminate/routing: ^10.0|^11.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-01-14 16:39:58 UTC
README
A Laravel package for integrating Geidea payment gateway with support for Egypt, Saudi Arabia, and UAE.
Features
- Multi-environment support (Egypt, KSA, UAE)
- Multi-currency support (EGP, SAR, AED)
- Multi-language support (Arabic, English)
- Session-based payment flow with HPP (Hosted Payment Page)
- Comprehensive logging
- Easy configuration
- Facade support
- Helper functions
Requirements
- PHP ^8.1|^8.2|^8.3|^8.4|^8.5
- Laravel ^10.0 or ^11.0
Note: PHP 8.5 is currently in pre-release. While this package declares support for it, some dependencies in your project may not yet be compatible. For production use, we recommend PHP 8.2, 8.3, or 8.4 (stable versions).
Installation
composer require almesery/laravel-geidea
Publish Configuration
php artisan vendor:publish --tag=geidea-config
Configuration
Add to your .env file:
GEIDEA_MERCHANT_PUBLIC_KEY=your_key GEIDEA_API_PASSWORD=your_password GEIDEA_ENVIRONMENT=ksa GEIDEA_CURRENCY=SAR GEIDEA_LANGUAGE=ar
Usage
Using Facade
use Almesery\LaravelGeidea\Facades\Geidea; // Create payment session $result = Geidea::createSession([ 'amount' => 100.00, 'currency' => 'SAR', 'merchant_reference_id' => 'INV123', 'callback_url' => route('payment.callback'), 'return_url' => route('payment.return'), 'customer' => [ 'email' => 'customer@example.com', 'name' => 'John Doe', 'phone_number' => '+966501234567', ], ]); if ($result['success']) { $sessionId = $result['session_id']; $orderId = $result['order_id']; // Redirect to checkout page with sessionId } else { // Handle error $errorMessage = $result['message']; } // Get order details $orderResult = Geidea::getOrder($orderId); if ($orderResult['success']) { $order = $orderResult['order']; $status = $order['status']; // Success, Failed, etc. } // Get HPP script URL $scriptUrl = Geidea::getHppScriptUrl(); // Get merchant public key $merchantKey = Geidea::getMerchantPublicKey();
Using Helper Functions
// Get service instance $geidea = geidea(); // Get service with custom config $geidea = geidea([ 'merchant_public_key' => 'custom_key', 'api_password' => 'custom_password', 'environment' => 'egypt', 'currency' => 'EGP', 'language' => 'en', ]); // Generate merchant reference $ref = geidea_merchant_reference('INV', 123); // Returns: INV123
Controller Example
Create a controller to handle payment flow:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Almesery\LaravelGeidea\Facades\Geidea; class PaymentController extends Controller { public function checkout(Request $request) { $amount = $request->input('amount'); $orderId = $request->input('order_id'); // Create payment session $result = Geidea::createSession([ 'amount' => $amount, 'currency' => 'SAR', 'merchant_reference_id' => geidea_merchant_reference('ORDER', $orderId), 'callback_url' => route('payment.callback'), 'return_url' => route('payment.return'), 'customer' => [ 'email' => auth()->user()->email, 'name' => auth()->user()->name, 'phone_number' => auth()->user()->phone, ], ]); if (!$result['success']) { return redirect()->back()->with('error', $result['message']); } return view('payment.checkout', [ 'sessionId' => $result['session_id'], 'amount' => $amount, 'currency' => 'SAR', ]); } public function callback(Request $request) { $orderId = $request->query('orderId'); if (!$orderId) { return redirect()->route('payment.failed') ->with('error', 'Invalid callback request'); } // Get order details $orderResult = Geidea::getOrder($orderId); if (!$orderResult['success']) { return redirect()->route('payment.failed') ->with('error', $orderResult['message']); } $order = $orderResult['order']; $status = $order['status']; if ($status === 'Success') { // Update your order status // Send confirmation email // etc. return redirect()->route('payment.success') ->with('order', $order); } return redirect()->route('payment.failed') ->with('error', 'Payment was not successful'); } }
Routes Example
Add these routes to your routes/web.php:
use App\Http\Controllers\PaymentController; Route::middleware(['auth'])->group(function () { Route::post('payment/checkout', [PaymentController::class, 'checkout'])->name('payment.checkout'); Route::get('payment/callback', [PaymentController::class, 'callback'])->name('payment.callback'); Route::get('payment/success', function () { return view('payment.success'); })->name('payment.success'); Route::get('payment/failed', function () { return view('payment.failed'); })->name('payment.failed'); });
Blade Views Examples
Checkout Page (resources/views/payment/checkout.blade.php)
<!DOCTYPE html> <html lang="{{ app()->getLocale() }}" dir="{{ app()->getLocale() == 'ar' ? 'rtl' : 'ltr' }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ __('Payment Checkout') }}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <style> body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; } .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.98); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 9999; } .loading-overlay.hidden { display: none; } .loading-spinner { width: 80px; height: 80px; border: 6px solid #f3f4f6; border-top: 6px solid #667eea; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> </head> <body> <div class="loading-overlay" id="loadingOverlay"> <div class="loading-spinner"></div> <p class="mt-3">{{ __('Loading payment...') }}</p> </div> <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card shadow-lg"> <div class="card-header bg-primary text-white text-center"> <h3>{{ __('Payment Checkout') }}</h3> </div> <div class="card-body"> <div class="alert alert-info"> <strong>{{ __('Amount') }}:</strong> {{ number_format($amount, 2) }} {{ $currency }} </div> <div id="geidea-payment-container"></div> </div> </div> </div> </div> </div> <script src="{{ Geidea::getHppScriptUrl() }}"></script> <script> function hideLoading() { document.getElementById('loadingOverlay').classList.add('hidden'); } try { const payment = new GeideaCheckout( function onSuccess(data) { console.log('Payment successful:', data); window.location.href = "{{ route('payment.callback') }}?orderId=" + data.order.orderId; }, function onError(data) { console.error('Payment error:', data); hideLoading(); alert('{{ __("Payment failed") }}: ' + (data.responseMessage || '{{ __("Unknown error") }}')); }, function onCancel() { console.log('Payment cancelled'); hideLoading(); if (confirm('{{ __("Are you sure you want to cancel the payment?") }}')) { window.location.href = "{{ url('/') }}"; } } ); // Start the payment with the session ID payment.startPayment("{{ $sessionId }}"); // Hide loading after 2 seconds setTimeout(hideLoading, 2000); } catch (error) { console.error('Failed to initialize payment:', error); hideLoading(); alert('{{ __("Failed to initialize payment. Please try again.") }}'); } </script> </body> </html>
Success Page (resources/views/payment/success.blade.php)
<!DOCTYPE html> <html lang="{{ app()->getLocale() }}" dir="{{ app()->getLocale() == 'ar' ? 'rtl' : 'ltr' }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ __('Payment Successful') }}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; } .success-checkmark { width: 80px; height: 115px; margin: 0 auto; } .check-icon { width: 80px; height: 80px; position: relative; border-radius: 50%; box-sizing: content-box; border: 4px solid #4caf50; } .check-icon::before { top: 3px; left: -2px; width: 30px; transform-origin: 100% 50%; border-radius: 100px 0 0 100px; } .check-icon::after { top: 0; left: 30px; width: 60px; transform-origin: 0 50%; border-radius: 0 100px 100px 0; animation: rotate-circle 4.25s ease-in; } .icon-line { height: 5px; background-color: #4caf50; display: block; border-radius: 2px; position: absolute; z-index: 10; } .icon-line-tip { top: 46px; left: 14px; width: 25px; transform: rotate(45deg); animation: icon-line-tip 0.75s; } .icon-line-long { top: 38px; right: 8px; width: 47px; transform: rotate(-45deg); animation: icon-line-long 0.75s; } @keyframes icon-line-tip { 0% { width: 0; left: 1px; top: 19px; } 54% { width: 0; left: 1px; top: 19px; } 70% { width: 50px; left: -8px; top: 37px; } 84% { width: 17px; left: 21px; top: 48px; } 100% { width: 25px; left: 14px; top: 45px; } } @keyframes icon-line-long { 0% { width: 0; right: 46px; top: 54px; } 65% { width: 0; right: 46px; top: 54px; } 84% { width: 55px; right: 0px; top: 35px; } 100% { width: 47px; right: 8px; top: 38px; } } </style> </head> <body> <div class="container"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow-lg"> <div class="card-body text-center py-5"> <div class="success-checkmark"> <div class="check-icon"> <span class="icon-line icon-line-tip"></span> <span class="icon-line icon-line-long"></span> <div class="icon-circle"></div> <div class="icon-fix"></div> </div> </div> <h2 class="fw-bold mt-4 mb-3 text-success">{{ __('Payment Successful!') }}</h2> <p class="text-muted">{{ __('Your payment has been processed successfully.') }}</p> @if(session('order')) <div class="mt-4"> <div class="alert alert-success"> <p class="mb-1"><strong>{{ __('Order ID') }}:</strong> {{ session('order')['orderId'] }}</p> @if(isset(session('order')['merchantReferenceId'])) <p class="mb-0"><strong>{{ __('Reference') }}:</strong> {{ session('order')['merchantReferenceId'] }}</p> @endif </div> </div> @endif <a href="{{ url('/') }}" class="btn btn-primary mt-3">{{ __('Go to Homepage') }}</a> </div> </div> </div> </div> </div> </body> </html>
Failed Page (resources/views/payment/failed.blade.php)
<!DOCTYPE html> <html lang="{{ app()->getLocale() }}" dir="{{ app()->getLocale() == 'ar' ? 'rtl' : 'ltr' }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ __('Payment Failed') }}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; } .error-icon { animation: shake 0.5s; } @keyframes shake { 0% { transform: translateX(0); } 25% { transform: translateX(-10px); } 50% { transform: translateX(10px); } 75% { transform: translateX(-10px); } 100% { transform: translateX(0); } } </style> </head> <body> <div class="container"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card shadow-lg"> <div class="card-body text-center py-5"> <div class="error-icon"> <i class="fas fa-times-circle text-danger" style="font-size: 5rem;"></i> </div> <h2 class="fw-bold mt-4 mb-3 text-danger">{{ __('Payment Failed') }}</h2> <p class="text-muted"> {{ session('error') ?? __('There was an error processing your payment. Please try again.') }} </p> <div class="mt-4"> <a href="{{ url()->previous() }}" class="btn btn-outline-primary me-2"> {{ __('Try Again') }} </a> <a href="{{ url('/') }}" class="btn btn-primary"> {{ __('Go to Homepage') }} </a> </div> </div> </div> </div> </div> </div> </body> </html>
API Reference
createSession(array $params)
Creates a payment session.
Parameters:
amount(float, required): Payment amountcurrency(string, optional): Currency code (EGP, SAR, AED). Defaults to config valuemerchant_reference_id(string, optional): Your unique reference IDcallback_url(string, required): URL to redirect after paymentreturn_url(string, optional): Alternative return URLlanguage(string, optional): Language code (ar, en). Defaults to config valuecustomer(array, optional):email(string)name(string)phone_number(string)
Returns:
[
'success' => true|false,
'session_id' => 'xxx',
'order_id' => 'xxx',
'payment_link' => 'xxx',
'data' => [...], // Full API response
'message' => 'error message' // Only on failure
]
getOrder(string $orderId)
Retrieves order details.
Parameters:
orderId(string, required): The Geidea order ID
Returns:
[
'success' => true|false,
'order' => [...], // Order details
'data' => [...], // Full API response
'message' => 'error message' // Only on failure
]
getHppScriptUrl()
Returns the HPP script URL for the current environment.
getMerchantPublicKey()
Returns the merchant public key.
Logging
All API calls are logged to storage/logs/geidea.log. The log includes:
- Session creation attempts
- Order retrieval attempts
- API responses
- Errors and exceptions
Testing
composer test
Environment Support
The package supports three environments:
Egypt
- API:
https://api.merchant.geidea.net - HPP:
https://www.merchant.geidea.net/hpp/geideaCheckout.min.js - Currency: EGP
Saudi Arabia (KSA)
- API:
https://api.ksamerchant.geidea.net - HPP:
https://www.ksamerchant.geidea.net/hpp/geideaCheckout.min.js - Currency: SAR
United Arab Emirates (UAE)
- API:
https://api.geidea.ae - HPP:
https://payments.geidea.ae/hpp/geideaCheckout.min.js - Currency: AED
Security
- All API calls use HTTPS
- HMAC-SHA256 signature for payment sessions
- Basic authentication with merchant credentials
- Sensitive data is never logged
License
MIT License - see LICENSE file for details
Support
For issues and questions, please open an issue on GitHub.
Credits
Developed by Almesery