fahipaydev / gateway-laravel
FahiPay Payment Gateway integration for Laravel
Package info
github.com/eragon2020-dev/fahipay-gateway-laravel
Type:laravel-package
pkg:composer/fahipaydev/gateway-laravel
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0|^13.0
- laravel/framework: ^11.0|^12.0|^13.0
- spatie/laravel-data: ^4.0
Requires (Dev)
- laravel/pint: ^1.14
- mockery/mockery: ^1.6
- nunomaduro/collision: ^8.1
- orchestra/testbench: ^9.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-laravel: ^2.4
- php-http/mock-client: ^1.6
Suggests
- livewire/livewire: Required for Livewire payment components
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>
Table of Contents
- Features
- Requirements
- Installation
- Configuration
- Quick Start
- Usage
- Database Integration
- Events
- Facades
- Routes
- Views
- Testing
- Examples
- Troubleshooting
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:
- Visit https://fahipay.mv/merchants/portal/
- Register or login to your merchant account
- 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 pageerror.blade.php- Payment error pagecancelled.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
Successparameter 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
- Documentation: https://fahipay.mv/merchants/portal/
- Issues: https://github.com/fahipay/gateway-laravel/issues
Changelog
1.0.0
- Initial release
- Basic payment functionality
- Event-driven architecture
- Database model included
- Artisan commands
- Test mode support# fahipay-gateway-laravel