https://github.com/hamoi1/laravel-fib-payment
Laravel package for integrating with the First Iraqi Bank (FIB) payment gateway, providing seamless payment processing and management
https://github.com/hamoi1/laravel-fib-payment
fib first-iraqi-bank gateway integration iqd iraq laravel package payment payment-gateway payment-integration php
Last synced: 2 months ago
JSON representation
Laravel package for integrating with the First Iraqi Bank (FIB) payment gateway, providing seamless payment processing and management
- Host: GitHub
- URL: https://github.com/hamoi1/laravel-fib-payment
- Owner: Hamoi1
- License: mit
- Created: 2026-04-01T17:44:15.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-05T10:55:08.000Z (2 months ago)
- Last Synced: 2026-04-07T10:03:08.192Z (2 months ago)
- Topics: fib, first-iraqi-bank, gateway, integration, iqd, iraq, laravel, package, payment, payment-gateway, payment-integration, php
- Language: PHP
- Homepage: https://fib.iq
- Size: 27.3 KB
- Stars: 3
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Laravel FIB Payment (First Iraqi Bank)
A modern, type-safe Laravel SDK for First Iraqi Bank (FIB) payment integration. This package provides a fluent API for payment creation, status tracking, cancellations, refunds, and webhook protection with full dependency injection support.
---
## Table of Contents
- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Architecture Overview](#architecture-overview)
- [Usage](#usage)
- [API Reference](#api-reference)
- [Error Handling](#error-handling)
- [Webhook Protection](#webhook-protection)
- [Testing](#testing)
- [Advanced Usage](#advanced-usage)
- [Troubleshooting](#troubleshooting)
- [License](#license)
---
## Requirements
- PHP 8.2+
- Laravel 11.x, 12.x, or 13.x
- FIB Payment Gateway credentials (client_id and client_secret)
---
## Installation
Install via Composer:
```bash
composer require hamoi1/laravel-fib-payment
```
The package will auto-register via Laravel's discovery. Publish the configuration file:
```bash
php artisan vendor:publish --tag=fib-config
```
---
## Configuration
Add the following to your `.env` file:
```env
# Required
FIB_CLIENT_ID=your-fib-client-id
FIB_CLIENT_SECRET=your-fib-client-secret
FIB_ENVIRONMENT=stage
# Optional
FIB_CALLBACK_URL=https://your-app.test/fib/webhook
FIB_CURRENCY=IQD
FIB_TIMEOUT=15
FIB_CONNECT_TIMEOUT=5
FIB_RETRY_TIMES=3
FIB_RETRY_DELAY=200
FIB_CACHE_DRIVER=
FIB_REFUNDABLE_FOR=P7D
FIB_ALLOWED_CALLBACK_IPS=
```
### Environment Variables Reference
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `FIB_CLIENT_ID` | Yes | - | OAuth2 client ID from FIB |
| `FIB_CLIENT_SECRET` | Yes | - | OAuth2 client secret from FIB |
| `FIB_ENVIRONMENT` | Yes | `stage` | Environment: `stage`, or `prod` |
| `FIB_CALLBACK_URL` | No | `http://127.0.0.1:8000/fib/callback` | Webhook endpoint for status updates |
| `FIB_CURRENCY` | No | `IQD` | Default currency: `IQD` or `USD` |
| `FIB_TIMEOUT` | No | `15` | HTTP request timeout in seconds |
| `FIB_CONNECT_TIMEOUT` | No | `5` | Connection timeout in seconds |
| `FIB_RETRY_TIMES` | No | `3` | Number of retry attempts for failed requests |
| `FIB_RETRY_DELAY` | No | `200` | Delay between retries in milliseconds |
| `FIB_CACHE_DRIVER` | No | - | Cache driver for OAuth tokens (null = default) |
| `FIB_REFUNDABLE_FOR` | No | `P7D` | ISO 8601 duration for refund window |
| `FIB_ALLOWED_CALLBACK_IPS` | No | - | Comma-separated IPs for webhook security |
### Environments
| Environment | Base URL | Use Case |
|-------------|----------|----------|
| `stage` | `https://fib.stage.fib.iq` | Testing with FIB staging apps |
| `prod` | `https://fib.prod.fib.iq` | Production transactions |
---
## Architecture Overview
### Design Principles
- **Dependency Injection**: No static methods - everything is injectable
- **Type Safety**: PHP 8.2+ features with `readonly` classes and strict types
- **DTO Pattern**: Data Transfer Objects for all API inputs/outputs
- **Enum Safety**: Enums for currencies, environments, statuses, and error reasons
- **Exception Hierarchy**: Specific exceptions for different error types
- **Token Caching**: Smart OAuth2 token caching with configurable drivers
## Usage
### 1. Quick Start with Facade
The simplest way to use the package:
```php
use Hamoi1\FibPayment\Facades\Fib;
use Hamoi1\FibPayment\Data\PaymentRequest;
use Hamoi1\FibPayment\Enums\Currency;
// Create a payment
$response = Fib::createPayment(new PaymentRequest(
amount: 25000,
currency: Currency::IQD,
description: 'Order #1024',
callbackUrl: route('fib.webhook'),
));
// Access response data
$paymentId = $response->paymentId;
$readableCode = $response->readableCode;
$qrCode = $response->qrCode;
```
### 2. Check Payment Status
```php
use Hamoi1\FibPayment\Facades\Fib;
$status = Fib::getPaymentStatus($paymentId);
// Helper methods
if ($status->isPaid()) {
// Payment completed successfully
$payerName = $status->paidBy?->name;
$payerIban = $status->paidBy?->iban;
}
if ($status->isUnpaid()) {
// Payment still pending
$expiresAt = $status->validUntil;
}
if ($status->isDeclined()) {
// Payment declined
$reason = $status->decliningReason?->value;
}
```
### 3. Cancel or Refund Payments
```php
// Cancel an unpaid payment
$cancelled = Fib::cancelPayment($paymentId); // bool
// Refund a paid payment (within refundable window)
$refunded = Fib::refundPayment($paymentId); // bool
```
### 4. Dependency Injection (Recommended)
For better testability and cleaner architecture:
```php
use Hamoi1\FibPayment\Contracts\FibClientInterface;
use Hamoi1\FibPayment\Data\PaymentRequest;
final class CheckoutController
{
public function __construct(
private readonly FibClientInterface $fib
) {}
public function store(Request $request): JsonResponse
{
$payment = $this->fib->createPayment(new PaymentRequest(
amount: $request->input('amount'),
description: "Order #{$request->input('order_id')}",
));
return response()->json([
'payment_id' => $payment->paymentId,
'code' => $payment->readableCode,
]);
}
}
```
---
## Error Handling
The package throws specific exceptions for different error scenarios:
### Exception Hierarchy
```
FibException (abstract base)
├── AuthenticationException (401/403)
├── RateLimitException (429)
└── PaymentFailedException (other HTTP errors)
```
### Handling Examples
```php
use Hamoi1\FibPayment\Exceptions\AuthenticationException;
use Hamoi1\FibPayment\Exceptions\RateLimitException;
use Hamoi1\FibPayment\Exceptions\PaymentFailedException;
use Hamoi1\FibPayment\Exceptions\FibException;
try {
$response = Fib::createPayment($request);
} catch (AuthenticationException $e) {
// Invalid credentials or token expired
// HTTP 401 or 403
Log::error('FIB auth failed: ' . $e->getMessage());
} catch (RateLimitException $e) {
// Too many requests
// HTTP 429
Log::warning('FIB rate limit hit');
// Retry after delay
} catch (PaymentFailedException $e) {
// Other API errors (500, 400, etc.)
Log::error('FIB payment error: ' . $e->getMessage());
} catch (FibException $e) {
// Catch-all for any package exception
Log::error('FIB error: ' . $e->getMessage());
}
```
---
## Webhook Protection
### Using the Middleware
Protect your webhook endpoint from invalid requests:
```php
use Hamoi1\FibPayment\Http\Middleware\VerifyFibWebhook;
use Illuminate\Support\Facades\Route;
Route::post('/fib/webhook', [PaymentController::class, 'handleWebhook'])
->middleware(VerifyFibWebhook::class)
->name('fib.webhook');
```
### Middleware Validation
The `VerifyFibWebhook` middleware validates:
1. **Required Fields**: Ensures `paymentId` and `status` are present
2. **Valid Status**: Checks status is one of `PAID`, `UNPAID`, or `DECLINED`
3. **IP Allowlist**: If `FIB_ALLOWED_CALLBACK_IPS` is set, validates request IP
### Webhook Controller Example
```php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Hamoi1\FibPayment\Enums\PaymentStatus;
final class PaymentController extends Controller
{
public function handleWebhook(Request $request): JsonResponse
{
$paymentId = $request->input('paymentId');
$status = PaymentStatus::from($request->input('status'));
match ($status) {
PaymentStatus::PAID => $this->markOrderAsPaid($paymentId),
PaymentStatus::DECLINED => $this->markOrderAsDeclined($paymentId),
PaymentStatus::UNPAID => null, // No action needed
};
return response()->json(['received' => true]);
}
private function markOrderAsPaid(string $paymentId): void
{
// Update your database
Order::where('fib_payment_id', $paymentId)
->update(['status' => 'paid']);
}
private function markOrderAsDeclined(string $paymentId): void
{
Order::where('fib_payment_id', $paymentId)
->update(['status' => 'declined']);
}
}
```
### IP Allowlist Configuration
Restrict webhooks to specific FIB server IPs:
```env
FIB_ALLOWED_CALLBACK_IPS=203.0.113.10,203.0.113.11
```
---
## Testing
### Mocking the Service
In your application tests, mock the interface:
```php
use Hamoi1\FibPayment\Contracts\FibClientInterface;
use Hamoi1\FibPayment\Data\PaymentResponse;
use Hamoi1\FibPayment\Data\PaymentStatusInfo;
use Hamoi1\FibPayment\Enums\PaymentStatus;
use Illuminate\Support\Facades\App;
use Mockery;
it('processes successful payment', function () {
// Create mock
$mock = Mockery::mock(FibClientInterface::class);
// Define expected behavior
$mock->shouldReceive('createPayment')
->once()
->andReturn(new PaymentResponse(
paymentId: 'pay-test-123',
readableCode: 'ABC123',
qrCode: 'base64-encoded-qr-data',
validUntil: now()->addHour()->toIso8601String(),
personalAppLink: 'https://fib.iq/personal/pay-test-123',
businessAppLink: 'https://fib.iq/business/pay-test-123',
corporateAppLink: 'https://fib.iq/corporate/pay-test-123',
));
// Bind mock to container
App::instance(FibClientInterface::class, $mock);
// Test your controller/service
$response = $this->postJson('/api/checkout', [
'amount' => 1000,
]);
$response->assertOk()
->assertJsonPath('payment_id', 'pay-test-123');
});
it('handles paid webhook', function () {
$mock = Mockery::mock(FibClientInterface::class);
$mock->shouldReceive('getPaymentStatus')
->with('pay-test-456')
->andReturn(new PaymentStatusInfo(
paymentId: 'pay-test-456',
status: PaymentStatus::PAID,
validUntil: now()->addHour()->toIso8601String(),
amount: new \Hamoi1\FibPayment\Data\MonetaryValue(1000, \Hamoi1\FibPayment\Enums\Currency::IQD),
));
App::instance(FibClientInterface::class, $mock);
$response = $this->postJson('/fib/webhook', [
'paymentId' => 'pay-test-456',
'status' => 'PAID',
]);
$response->assertOk();
});
```
### Package Quality Commands
Run these commands to ensure code quality:
```bash
# Run all checks
composer check
# Individual commands
composer lint # Laravel Pint code style check
composer lint:fix # Auto-fix code style
composer analyse # PHPStan static analysis
composer test # Pest tests
```
---
## Troubleshooting
### Common Issues
| Issue | Solution |
|-------|----------|
| `Jwt issuer is not configured` | Your client_id isn't activated by FIB. Contact FIB support. |
| `401 Unauthorized` | Check client_id and client_secret in .env |
| `403 Forbidden` | Credentials may be valid but lack permissions |
| `429 Too Many Requests` | You're hitting rate limits. Implement exponential backoff. |
| Token cache issues | Run `php artisan cache:clear` |
| Config not loading | Run `php artisan config:clear` |
### Debug Endpoints
Add this to your routes for debugging:
```php
Route::get('/fib/debug', function () {
return [
'environment' => config('fib.environment'),
'client_id' => config('fib.client_id') ? '***' . substr(config('fib.client_id'), -4) : 'NOT SET',
'client_secret_set' => ! empty(config('fib.client_secret')),
'callback_url' => config('fib.callback_url'),
'base_url' => \Hamoi1\FibPayment\Enums\FibEnvironment::from(config('fib.environment'))->baseUrl(),
];
});
```
---
## License
The MIT License (MIT). Please see [LICENSE](LICENSE) for more information.