fahipaydev/gateway-laravel

FahiPay Payment Gateway integration for Laravel

Maintainers

Package info

github.com/eragon2020-dev/fahipay-gateway-laravel

Type:laravel-package

pkg:composer/fahipaydev/gateway-laravel

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-04-15 15:57 UTC

This package is auto-updated.

Last update: 2026-04-15 16:01:05 UTC


README

FahiPay Payment Gateway integration for Laravel 13. Accept payments from Maldives using FahiPay's secure payment infrastructure.

API Endpoints:

  • Create Transaction: POST https://fahipay.mv/api/merchants/createTxn/
  • Query Transaction: GET https://fahipay.mv/api/merchants/getTxn/?mref=<TransactionID>

Latest Version on Packagist PHP Version License

Table of Contents

Features

  • Easy integration with FahiPay payment gateway
  • Support for test mode
  • Event-driven architecture
  • Database model for payment tracking
  • Built-in callback handling
  • Artisan commands for testing
  • Multiple payment methods
  • Secure signature verification

Requirements

  • PHP 8.2+
  • Laravel 13.0+
  • PHP cURL extension
  • PHP JSON extension

Installation

Step 1: Install via Composer

composer require fahipay/gateway-laravel

Step 2: Run Installation Command

php artisan fahipay:install

This command will:

  • Publish the configuration file (config/fahipay.php)
  • Publish database migrations

Step 3: Run Migrations

php artisan migrate

Configuration

Step 1: Configure Environment Variables

Add your FahiPay credentials to your .env file:

# FahiPay Configuration
FAHIPAY_MERCHANT_ID=your_merchant_id
FAHIPAY_SECRET_KEY=your_secret_key

# Test Mode (set to false for production)
FAHIPAY_TEST_MODE=true

# URLs (optional - defaults are provided)
FAHIPAY_RETURN_URL=/fahipay/callback/success
FAHIPAY_CANCEL_URL=/fahipay/callback/cancel
FAHIPAY_ERROR_URL=/fahipay/callback/error

To get your credentials:

  1. Visit https://fahipay.mv/merchants/portal/
  2. Register or login to your merchant account
  3. Get your Merchant ID and Secret Key

Step 2: Verify Configuration

Check your config/fahipay.php after publishing:

// config/fahipay.php
<?php

return [
    'merchant_id' => env('FAHIPAY_MERCHANT_ID', ''),
    'secret_key' => env('FAHIPAY_SECRET_KEY', ''),
    'test_mode' => env('FAHIPAY_TEST_MODE', false),
    // ... other config options
];

Quick Start

Create a Payment (5 Lines of Code!)

use Fahipay\Gateway\Facades\FahipayGateway;

// Create payment and get redirect URL
$payment = FahipayGateway::createPayment('ORDER-001', 100.00);

// Redirect user to FahiPay
return redirect($payment->paymentUrl);

That's it! 🎉

Usage

Basic Payment

use Fahipay\Gateway\Facades\FahipayGateway;

public function checkout(Request $request)
{
    // Generate unique transaction ID
    $transactionId = 'PAY-' . uniqid();
    
    // Create payment
    $payment = FahipayGateway::createPayment(
        transactionId: $transactionId,
        amount: $request->input('amount'),
        description: 'Order Payment'
    );
    
    // Redirect to payment page
    return redirect($payment->paymentUrl);
}

With Redirect (Controller Method)

public function initiatePayment(Request $request)
{
    $transactionId = 'ORDER-' . time();
    $amount = $request->amount;
    
    $payment = FahipayGateway::createPayment($transactionId, $amount);
    
    if ($payment->paymentUrl) {
        return redirect($payment->paymentUrl);
    }
    
    return back()->with('error', 'Failed to create payment');
}

Check Payment Status

use Fahipay\Gateway\Facades\FahipayGateway;

public function checkStatus(string $transactionId)
{
    $transaction = FahipayGateway::getTransaction($transactionId);
    
    if ($transaction) {
        // Transaction found
        return response()->json([
            'status' => $transaction->status->value,
            'amount' => $transaction->amount,
            'approval_code' => $transaction->approvalCode,
        ]);
    }
    
    return response()->json(['error' => 'Transaction not found'], 404);
}

Handle Callbacks

The package provides built-in routes for callbacks. Just handle the events:

Option 1: Event Listeners (Recommended)

Create an event listener in your app:

// app/Listeners/HandlePaymentCompleted.php

namespace App\Listeners;

use Fahipay\Gateway\Events\PaymentCompletedEvent;

class HandlePaymentCompleted
{
    public function handle(PaymentCompletedEvent $event)
    {
        // Find and update your order
        $order = Order::where('transaction_id', $event->transactionId)->first();
        
        if ($order) {
            $order->update([
                'payment_status' => 'paid',
                'approval_code' => $event->approvalCode,
            ]);
            
            // Send confirmation email, etc.
        }
    }
}

Register the listener in your EventServiceProvider:

// app/Providers/EventServiceProvider.php

protected $listen = [
    \Fahipay\Gateway\Events\PaymentCompletedEvent::class => [
        \App\Listeners\HandlePaymentCompleted::class,
    ],
    \Fahipay\Gateway\Events\PaymentFailedEvent::class => [
        \App\Listeners\HandlePaymentFailed::class,
    ],
    \Fahipay\Gateway\Events\PaymentCancelledEvent::class => [
        \App\Listeners\HandlePaymentCancelled::class,
    ],
];

Option 2: Direct Route Handling

// routes/web.php

Route::get('/payment/callback', function (\Illuminate\Http\Request $request) {
    $gateway = app(\Fahipay\Gateway\FahipayGateway::class);
    
    // Validate signature
    if (!$gateway->validateCallback($request)) {
        return response('Invalid signature', 403);
    }
    
    $success = $request->get('Success');
    $transactionId = $request->get('ShoppingCartID');
    $approvalCode = $request->get('ApprovalCode');
    
    if ($success === 'true') {
        // Update your order status
        Order::where('transaction_id', $transactionId)
            ->update(['status' => 'paid', 'approval_code' => $approvalCode]);
            
        return redirect('/payment/success');
    }
    
    return redirect('/payment/failed');
});

Database Integration

Using the Model

The package includes a FahipayPayment model:

use Fahipay\Gateway\Models\FahipayPayment;

// Create payment record
$payment = FahipayPayment::createPayment(
    transactionId: 'ORDER-001',
    merchantId: 'merchant123',
    amount: 100.00,
    description: 'Test payment'
);

// Update status when completed
$payment->markAsCompleted('APPROVAL123');

// Or mark as failed
$payment->markAsFailed('Card declined');

// Query payments
$pendingPayments = FahipayPayment::pending()->get();
$completedPayments = FahipayPayment::completed()->get();

Custom Database Table

If you have your own orders table, you can add a transaction_id column:

Schema::table('orders', function (Blueprint $table) {
    $table->string('transaction_id')->nullable()->unique();
    $table->string('approval_code')->nullable();
    $table->enum('payment_status', ['pending', 'paid', 'failed'])->default('pending');
});

Events

The package dispatches the following events:

Event Description
PaymentInitiatedEvent When a payment is created
PaymentCompletedEvent When payment is successful
PaymentFailedEvent When payment fails
PaymentCancelledEvent When user cancels payment

Example: Listening to Events

// In a ServiceProvider's boot method
Event::listen(\Fahipay\Gateway\Events\PaymentCompletedEvent::class, function ($event) {
    Log::info("Payment completed: {$event->transactionId}");
    // Update your database, send notifications, etc.
});

Facades

Using Facade

use Fahipay\Gateway\Facades\FahipayGateway;

// Create payment
$payment = FahipayGateway::createPayment('TXN-001', 50.00);

// Get payment URL
$url = FahipayGateway::getPaymentUrl('TXN-001', 50.00);

// Check status
$transaction = FahipayGateway::getTransaction('TXN-001');

// Validate callback
$isValid = FahipayGateway::validateCallback($request);

// Custom configuration
FahipayGateway::setTestMode(true)
    ->setReturnUrl('https://yoursite.com/payment/return')
    ->createPayment('TXN-002', 75.00);

Using Dependency Injection

use Fahipay\Gateway\FahipayGateway;

class PaymentController extends Controller
{
    public function __construct(
        protected FahipayGateway $fahipay
    ) {}

    public function create(Request $request)
    {
        $payment = $this->fahipay->createPayment(
            'TXN-' . time(),
            $request->amount
        );
        
        return redirect($payment->paymentUrl);
    }
}

Routes

By default, the package automatically registers these routes:

Route Description
GET /fahipay/callback Main callback handler
GET /fahipay/callback/success Success page
GET /fahipay/callback/cancel Cancel page
GET /fahipay/callback/error Error page
POST /fahipay/webhook Webhook handler

Disable Default Routes

If you want to use your own routes, disable the default:

// config/fahipay.php
'routes' => [
    'enabled' => false,
],

Then create your own:

Route::post('/payment/fahipay/webhook', [PaymentController::class, 'webhook']);

Views

The package provides default views in resources/views/fahipay/:

  • success.blade.php - Payment success page
  • error.blade.php - Payment error page
  • cancelled.blade.php - Payment cancelled page

Publish Views

php artisan vendor:publish --tag=fahipay-views

Custom Views

Create your own views and use them:

// In your controller
return view('payments.fahipay.success', [
    'transactionId' => $transactionId,
    'approvalCode' => $approvalCode,
]);

Testing

Using Artisan Commands

# Create a test payment
php artisan fahipay:create TEST-001 100.00 --description="Test payment"

# Check payment status
php artisan fahipay:check TEST-001

Unit Testing

<?php

use Fahipay\Gateway\Facades\FahipayGateway;

test('can create payment', function () {
    FahipayGateway::setTestMode(true);
    
    $payment = FahipayGateway::createPayment('TEST-001', 100.00);
    
    expect($payment->transactionId)->toBe('TEST-001');
    expect($payment->amount)->toBe(100.00);
    expect($payment->status->value)->toBe('pending');
});

test('can validate signature', function () {
    $gateway = app(FahipayGateway::class);
    
    $signature = $gateway->generateSignature('TXN-001', 100.00, '2024-01-01 12:00:00');
    
    expect($signature)->toBeString();
});

Examples

Example 1: Staff Fee Payment

This is a complete example for a staff monthly fee portal.

Step 1: Create Migration

php artisan make:migration create_staff_fees_table
// database/migrations/xxxx_xx_xx_create_staff_fees_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('staff_fees', function (Blueprint $table) {
            $table->id();
            $table->string('staff_id');
            $table->string('staff_name');
            $table->decimal('amount', 10, 2);
            $table->string('month'); // e.g., "April 2026"
            $table->string('transaction_id')->nullable()->unique();
            $table->string('approval_code')->nullable();
            $table->enum('status', ['unpaid', 'paid', 'failed'])->default('unpaid');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('staff_fees');
    }
};

Step 2: Create Model

// app/Models/StaffFee.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class StaffFee extends Model
{
    protected $fillable = [
        'staff_id',
        'staff_name',
        'amount',
        'month',
        'transaction_id',
        'approval_code',
        'status',
    ];

    protected $casts = [
        'amount' => 'decimal:2',
    ];
}

Step 3: Create Controller

// app/Http/Controllers/StaffFeeController.php

namespace App\Http\Controllers;

use App\Models\StaffFee;
use Fahipay\Gateway\Facades\FahipayGateway;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class StaffFeeController extends Controller
{
    public function index()
    {
        $fees = StaffFee::where('staff_id', auth()->id())->get();
        return view('staff.fees.index', compact('fees'));
    }

    public function pay(StaffFee $fee)
    {
        // Generate unique transaction ID
        $transactionId = 'FEE-' . $fee->id . '-' . Str::random(8);
        
        // Update fee with transaction ID
        $fee->update(['transaction_id' => $transactionId]);
        
        // Create FahiPay payment
        $payment = FahipayGateway::createPayment(
            transactionId: $transactionId,
            amount: $fee->amount,
            description: "Monthly fee for {$fee->month}"
        );
        
        // Redirect to FahiPay
        return redirect($payment->paymentUrl);
    }

    public function callback(Request $request)
    {
        // Validate callback
        $gateway = app(\Fahipay\Gateway\FahipayGateway::class);
        
        if (!$gateway->validateCallback($request)) {
            return response('Invalid signature', 403);
        }
        
        $transactionId = $request->get('ShoppingCartID');
        $success = $request->get('Success') === 'true';
        $approvalCode = $request->get('ApprovalCode');
        
        // Find the fee
        $fee = StaffFee::where('transaction_id', $transactionId)->first();
        
        if (!$fee) {
            return response('Transaction not found', 404);
        }
        
        // Update status
        if ($success) {
            $fee->update([
                'status' => 'paid',
                'approval_code' => $approvalCode,
            ]);
            return redirect('/staff/fees?status=success');
        }
        
        $fee->update(['status' => 'failed']);
        return redirect('/staff/fees?status=failed');
    }
}

Step 4: Add Routes

// routes/web.php

use App\Http\Controllers\StaffFeeController;

Route::middleware(['auth'])->group(function () {
    Route::get('/staff/fees', [StaffFeeController::class, 'index'])->name('staff.fees');
    Route::post('/staff/fees/{fee}/pay', [StaffFeeController::class, 'pay'])->name('staff.fees.pay');
    Route::get('/staff/fees/callback', [StaffFeeController::class, 'callback'])->name('staff.fees.callback');
});

Step 5: Create View

<!-- resources/views/staff/fees/index.blade.php -->

@extends('layouts.app')

@section('content')
<h1>My Monthly Fees</h1>

@if(session('status') === 'success')
    <div class="alert alert-success">Payment successful!</div>
@elseif(session('status') === 'failed')
    <div class="alert alert-danger">Payment failed. Please try again.</div>
@endif

<table class="table">
    <thead>
        <tr>
            <th>Month</th>
            <th>Amount</th>
            <th>Status</th>
            <th>Action</th>
        </tr>
    </thead>
    <tbody>
        @foreach($fees as $fee)
        <tr>
            <td>{{ $fee->month }}</td>
            <td>MVR {{ number_format($fee->amount, 2) }}</td>
            <td>
                <span class="badge bg-{{ $fee->status === 'paid' ? 'success' : 'warning' }}">
                    {{ ucfirst($fee->status) }}
                </span>
            </td>
            <td>
                @if($fee->status === 'unpaid')
                    <form action="{{ route('staff.fees.pay', $fee) }}" method="POST">
                        @csrf
                        <button type="submit" class="btn btn-primary">Pay Now</button>
                    </form>
                @else
                    <button class="btn btn-secondary" disabled>Paid</button>
                @endif
            </td>
        </tr>
        @endforeach
    </tbody>
</table>
@endsection

Example 2: Subscription Payment

// app/Http/Controllers/SubscriptionController.php

namespace App\Http\Controllers;

use App\Models\Subscription;
use Fahipay\Gateway\Facades\FahipayGateway;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class SubscriptionController extends Controller
{
    public function subscribe(Request $request)
    {
        $plan = $request->input('plan');
        $amount = match ($plan) {
            'basic' => 100,
            'premium' => 250,
            default => 100,
        };
        
        $transactionId = 'SUB-' . auth()->id() . '-' . time();
        
        // Create subscription record
        $subscription = Subscription::create([
            'user_id' => auth()->id(),
            'plan' => $plan,
            'amount' => $amount,
            'transaction_id' => $transactionId,
            'status' => 'pending',
        ]);
        
        // Create payment
        $payment = FahipayGateway::createPayment(
            transactionId: $transactionId,
            amount: $amount,
            description: "Subscription - {$plan} plan"
        );
        
        return redirect($payment->paymentUrl);
    }

    public function handleCallback(Request $request)
    {
        $gateway = app(\Fahipay\Gateway\FahipayGateway::class);
        
        if (!$gateway->validateCallback($request)) {
            return response('Invalid signature', 403);
        }
        
        $transactionId = $request->get('ShoppingCartID');
        $success = $request->get('Success') === 'true';
        
        $subscription = Subscription::where('transaction_id', $transactionId)->first();
        
        if ($success && $subscription) {
            $subscription->update([
                'status' => 'active',
                'approval_code' => $request->get('ApprovalCode'),
                'started_at' => now(),
            ]);
            
            return redirect('/subscription/success');
        }
        
        $subscription?->update(['status' => 'failed']);
        return redirect('/subscription/failed');
    }
}

Example 3: Event Registration Payment

// app/Http/Controllers/EventRegistrationController.php

namespace App\Http\Controllers;

use App\Models\EventRegistration;
use Fahipay\Gateway\Facades\FahipayGateway;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class EventRegistrationController extends Controller
{
    public function register(Request $request)
    {
        $event = Event::findOrFail($request->event_id);
        
        $transactionId = 'EVT-' . $event->id . '-' . auth()->id() . '-' . time();
        
        $registration = EventRegistration::create([
            'event_id' => $event->id,
            'user_id' => auth()->id(),
            'transaction_id' => $transactionId,
            'amount' => $event->price,
            'status' => 'pending',
        ]);
        
        $payment = FahipayGateway::createPayment(
            transactionId: $transactionId,
            amount: $event->price,
            description: "Event: {$event->title}"
        );
        
        return redirect($payment->paymentUrl);
    }

    public function callback(Request $request)
    {
        $gateway = app(\Fahipay\Gateway\FahipayGateway::class);
        
        if (!$gateway->validateCallback($request)) {
            return response('Invalid signature', 403);
        }
        
        $transactionId = $request->get('ShoppingCartID');
        $success = $request->get('Success') === 'true';
        
        $registration = EventRegistration::where('transaction_id', $transactionId)->first();
        
        if ($success && $registration) {
            $registration->update([
                'status' => 'confirmed',
                'approval_code' => $request->get('ApprovalCode'),
            ]);
            
            // Send confirmation email
            // Mail::to($registration->user)->send(new RegistrationConfirmed($registration));
        } else {
            $registration?->update(['status' => 'cancelled']);
        }
        
        return redirect('/events/registration/success');
    }
}

Troubleshooting

Common Issues

1. "Merchant ID and Secret Key are required"

// Check your configuration
dump(config('fahipay.merchant_id'));
dump(config('fahipay.secret_key'));

// Make sure .env is loaded
php artisan config:clear
php artisan cache:clear

2. Signature Validation Failed

// Check your secret key
$gateway = app(\Fahipay\Gateway\FahipayGateway::class);
$signature = $gateway->generateSignature($transactionId, $amount, $timestamp);

// Verify it matches what FahiPay expects

3. Payment Link Returns 404

// Make sure you're using the correct test mode URL
// Test mode: https://test.fahipay.mv/api/merchants
// Production: https://fahipay.mv/api/merchants

4. Callback Not Received

// Check your logs
tail -f storage/logs/laravel.log

// Verify your return URL is accessible from the internet
// (FahiPay servers need to reach your callback URL)

5. Database Connection Error

// Check database configuration
php artisan tinker
DB::connection()->getPdo();

Debug Mode

Enable detailed logging:

// .env
FAHIPAY_LOGGING=true
FAHIPAY_LOG_CHANNEL=stack

Test Your Integration

# Create test payment
php artisan fahipay:create TEST-001 10.00

# Check its status
php artisan fahipay:check TEST-001

Security

  • Always validate callback signatures
  • Never trust the Success parameter alone
  • Store transaction IDs in your database
  • Use HTTPS in production
  • Keep your secret key secure
  • Log all payment attempts

Advanced Configuration

Custom Timeout

// config/fahipay.php
'timeout' => 60, // seconds

Multiple Merchant Accounts

// Using different merchant for different payments
$gateway = new \Fahipay\Gateway\FahipayGateway();
$gateway->setMerchantId('merchant2')
       ->setSecretKey('secret2')
       ->setTestMode(true)
       ->createPayment('TXN-002', 50.00);

Custom API Endpoints

// config/fahipay.php
'base_url' => 'https://custom-api.example.com',
'test_mode_url' => 'https://custom-test-api.example.com',

License

MIT License. See LICENSE for more information.

Support

Changelog

1.0.0

  • Initial release
  • Basic payment functionality
  • Event-driven architecture
  • Database model included
  • Artisan commands
  • Test mode support# fahipay-gateway-laravel