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

1.0.0 2026-01-14 15:53 UTC

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 amount
  • currency (string, optional): Currency code (EGP, SAR, AED). Defaults to config value
  • merchant_reference_id (string, optional): Your unique reference ID
  • callback_url (string, required): URL to redirect after payment
  • return_url (string, optional): Alternative return URL
  • language (string, optional): Language code (ar, en). Defaults to config value
  • customer (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