laravel-payment-gateways maintained by mohamed-saad
Laravel Payment Gateways
Laravel package for using multiple payment gateways from one clean API.
Supported gateways:
- Paymob
- MyFatoorah
- Tabby
- Tamara
The package gives you:
- One fluent checkout builder:
Payment::via('gateway')->...->createCheckout() - A facade for every gateway:
Paymob,MyFatoorah,Tabby,Tamara - Unified response DTOs for checkout, status, refunds, and webhooks
- One webhook route for all gateways:
POST /payment-webhooks/{gateway} - Publishable config and migration stubs
Installation
1. Install With Composer
From your Laravel project root:
composer require mohamed-saad/laravel-payment-gateways
Laravel auto-discovery registers the service provider and facades automatically. After installation, you can use the package anywhere in your Laravel app through the facades:
use MohamedSaad\PaymentGateways\Facades\Payment;
use MohamedSaad\PaymentGateways\Facades\Paymob;
use MohamedSaad\PaymentGateways\Facades\MyFatoorah;
use MohamedSaad\PaymentGateways\Facades\Tabby;
use MohamedSaad\PaymentGateways\Facades\Tamara;
2. Publish Config And Migration
php artisan vendor:publish --tag=payment-gateways-config
php artisan vendor:publish --tag=payment-gateways-migrations
php artisan migrate
3. Add Environment Keys
PAYMENT_GATEWAY_DEFAULT=paymob
PAYMENT_GATEWAY_WEBHOOK_PATH=payment-webhooks
PAYMOB_BASE_URL=https://accept.paymob.com
PAYMOB_SECRET_KEY=
PAYMOB_PUBLIC_KEY=
PAYMOB_PAYMENT_METHODS=card
PAYMOB_HMAC_SECRET=
MYFATOORAH_API_KEY=
MYFATOORAH_COUNTRY=SA
MYFATOORAH_TEST_MODE=true
MYFATOORAH_NOTIFICATION_OPTION=LNK
TABBY_SECRET_KEY=
TABBY_PUBLIC_KEY=
TABBY_MERCHANT_CODE=
TABBY_BASE_URL=https://api.tabby.ai
TAMARA_API_TOKEN=
TAMARA_NOTIFICATION_TOKEN=
TAMARA_BASE_URL=https://api-sandbox.tamara.co
Paymob base URLs by country:
# Egypt
PAYMOB_BASE_URL=https://accept.paymob.com
# Oman
PAYMOB_BASE_URL=https://oman.paymob.com
# Saudi Arabia
PAYMOB_BASE_URL=https://ksa.paymob.com
# UAE
PAYMOB_BASE_URL=https://uae.paymob.com
Use https://api.tabby.sa for Tabby KSA production. Use Tamara production URL when your Tamara account is live.
Basic Usage
Use the generic facade when the gateway is dynamic:
use MohamedSaad\PaymentGateways\Facades\Payment;
$checkout = Payment::via('tabby')
->amount(150)
->currency('SAR')
->customer([
'name' => 'Mona Lisa',
'email' => 'customer@email.com',
'phone' => '566027755',
])
->items([
[
'name' => 'Lego City 8601',
'quantity' => 1,
'unit_amount' => 150,
'category' => 'Toys',
],
])
->callback(
route('payment.success'),
route('payment.cancel'),
route('payment.failure')
)
->orderReference('ORDER-1001')
->createCheckout();
return redirect($checkout->url);
Or use the direct gateway facade:
use MohamedSaad\PaymentGateways\Facades\Tamara;
$checkout = Tamara::amount(300)
->currency('SAR')
->customer($user)
->items($items)
->callback(route('payment.success'), route('payment.cancel'), route('payment.failure'))
->orderReference($order->id)
->createCheckout();
Gateway Examples
Paymob
Paymob flow: create an intention, receive a client_secret, then redirect to Paymob Unified Checkout.
use MohamedSaad\PaymentGateways\Facades\Paymob;
$checkout = Paymob::amount(500)
->currency('EGP')
->customer([
'name' => 'Ahmed Ali',
'email' => 'ahmed@example.com',
'phone' => '01000000000',
])
->callback(
route('payment.success'),
route('payment.cancel'),
route('payment.failure')
)
->items([
[
'name' => 'Order items',
'quantity' => 1,
'unit_amount' => 500,
],
])
->orderReference('PAYMOB-ORDER-1001')
->meta([
'payment_methods' => ['card'], // or integration IDs like [1256]
'notification_url' => 'https://your-domain.com/payment-webhooks/paymob',
'billing_data' => [
'city' => 'Cairo',
'country' => 'EG',
'street' => 'NA',
],
])
->createCheckout();
return redirect($checkout->url);
Important Paymob values:
$checkout->checkoutId; // Paymob intention id
$checkout->referenceCode; // Paymob order id / intention_order_id
$checkout->url; // Unified Checkout URL
$checkout->raw; // Full Paymob intention response, including client_secret
Paymob callbacks are sent to notification_url. Add this URL in the intention payload or in your dashboard:
https://your-domain.com/payment-webhooks/paymob
For old Paymob transaction inquiry/refund endpoints, also set the legacy API key:
PAYMOB_API_KEY=
MyFatoorah
MyFatoorah flow: send payment, redirect customer to invoice URL, then verify invoice status.
use MohamedSaad\PaymentGateways\Facades\MyFatoorah;
$checkout = MyFatoorah::amount(250)
->currency('SAR')
->customer([
'name' => 'Mona Lisa',
'email' => 'mona@example.com',
'phone' => '0500000000',
])
->callback(
route('payment.success'),
route('payment.cancel'),
route('payment.failure')
)
->items([
[
'name' => 'Order items',
'quantity' => 1,
'unit_amount' => 250,
],
])
->orderReference('MYFATOORAH-ORDER-1001')
->meta([
'notification_option' => 'LNK',
'language' => 'en',
'webhook_url' => 'https://your-domain.com/payment-webhooks/myfatoorah',
])
->createCheckout();
return redirect($checkout->url);
Verify payment:
$status = MyFatoorah::verifyPayment($invoiceId);
Refund:
$refund = MyFatoorah::refund($paymentId, 50);
Callback URL:
https://your-domain.com/payment-webhooks/myfatoorah
Tabby
Tabby checkout endpoint:
POST https://api.tabby.ai/api/v2/checkout
Example:
use MohamedSaad\PaymentGateways\Facades\Tabby;
$checkout = Tabby::amount(100)
->currency('AED')
->customer([
'name' => 'John Doe',
'email' => 'jsmith@example.com',
'phone' => '500000001',
])
->items([
[
'title' => 'Name of the product',
'quantity' => 1,
'unit_price' => '100.00',
'category' => 'Clothes',
'reference_id' => 'SKU123',
'description' => 'Description of the product',
'image_url' => 'https://example.com/image.jpg',
'product_url' => 'https://example.com/product',
'brand' => 'Brand Name',
'is_refundable' => true,
],
])
->callback(
'https://your-store/success',
'https://your-store/cancel',
'https://your-store/failure'
)
->orderReference('TABBY-ORDER-1001')
->meta([
'lang' => 'en',
'buyer_dob' => '2000-01-20',
'shipping_address' => [
'city' => 'Dubai',
'address' => 'Dubai',
'zip' => '1111',
],
'buyer_history' => [
'registered_since' => now()->subYear()->toIso8601String(),
'loyalty_level' => 2,
],
'order_history' => [],
])
->createCheckout();
return redirect($checkout->url);
Important Tabby values:
$checkout->checkoutId; // Tabby session id
$checkout->referenceCode; // Tabby payment id, save this
$checkout->url; // Redirect URL
Verify, capture, and refund:
$status = Tabby::verifyPayment($paymentId);
$captured = Tabby::capture($paymentId, 100);
$refund = Tabby::refund($paymentId, 50);
Register Tabby webhook:
Tabby::registerWebhook(
'https://your-domain.com/payment-webhooks/tabby',
[
'title' => 'X-Webhook-Token',
'value' => 'your-random-webhook-token',
]
);
Tabby registers webhooks per merchant_code and per environment. A test secret key registers test webhooks, and a live secret key registers production webhooks.
More details: Tabby README
Tamara
Tamara checkout endpoint:
POST https://{environment}.tamara.co/checkout
Sandbox example:
POST https://api-sandbox.tamara.co/checkout
Example:
use MohamedSaad\PaymentGateways\Facades\Tamara;
$checkout = Tamara::amount(300)
->currency('SAR')
->customer([
'name' => 'Mona Lisa',
'email' => 'customer@email.com',
'phone' => '566027755',
])
->items([
[
'name' => 'Lego City 8601',
'type' => 'Digital',
'reference_id' => '123',
'sku' => 'SA-12436',
'quantity' => 1,
'unit_amount' => 490,
'discount_amount' => ['amount' => 100, 'currency' => 'SAR'],
'tax_amount' => ['amount' => 10, 'currency' => 'SAR'],
'item_url' => 'https://item-url.com/1234',
'image_url' => 'https://image-url.com/1234',
],
])
->callback(
'http://example.com/#/success',
'http://example.com/#/cancel',
'http://example.com/#/fail'
)
->orderReference('abd12331-a123-1234-4567-fbde34ae')
->meta([
'order_number' => 'A123125',
'shipping_amount' => ['amount' => 1, 'currency' => 'SAR'],
'tax_amount' => ['amount' => 1, 'currency' => 'SAR'],
'discount' => [
'amount' => ['amount' => 0, 'currency' => 'SAR'],
],
'country_code' => 'SA',
'description' => 'Enter order description here.',
'billing_address' => [
'city' => 'Riyadh',
'country_code' => 'SA',
'first_name' => 'Mona',
'last_name' => 'Lisa',
'line1' => '3764 Al Urubah Rd',
'line2' => 'string',
'phone_number' => '532298658',
'region' => 'As Sulimaniyah',
],
'shipping_address' => [
'city' => 'Riyadh',
'country_code' => 'SA',
'first_name' => 'Mona',
'last_name' => 'Lisa',
'line1' => '3764 Al Urubah Rd',
'line2' => 'string',
'phone_number' => '532298658',
'region' => 'As Sulimaniyah',
],
'platform' => 'Laravel',
'is_mobile' => false,
'locale' => 'ar_SA',
'risk_assessment' => [
'customer_age' => 21,
'customer_dob' => '01-12-2000',
'customer_gender' => 'Female',
'customer_nationality' => 'SA',
'is_premium_customer' => false,
'is_existing_customer' => false,
'is_guest_user' => false,
],
'additional_data' => [
'delivery_method' => 'Home Delivery',
'pickup_store' => 'Store A',
'store_code' => 'Branch A',
],
])
->createCheckout();
return redirect($checkout->url);
Important Tamara values:
$checkout->checkoutId; // Tamara checkout_id
$checkout->referenceCode; // Tamara order_id
$checkout->url; // Redirect URL
Verify, authorise, and refund:
$status = Tamara::verifyPayment($orderId);
$authorised = Tamara::authorise($orderId);
$refund = Tamara::refund($orderId, 50);
Register Tamara webhook:
Tamara::registerWebhook([
'type' => 'order',
'events' => [
'order_approved',
'order_authorised',
'order_canceled',
'order_updated',
'order_captured',
'order_refunded',
],
'url' => 'https://your-domain.com/payment-webhooks/tamara',
'headers' => [
'authorization' => 'your-webhook-authorization-token',
],
]);
You can also add the webhook URL manually in the Tamara dashboard:
https://your-domain.com/payment-webhooks/tamara
More details: Tamara README
Webhooks
The package registers one route:
POST /payment-webhooks/{gateway}
Examples:
POST /payment-webhooks/paymob
POST /payment-webhooks/myfatoorah
POST /payment-webhooks/tabby
POST /payment-webhooks/tamara
Listen for payment status changes:
use Illuminate\Support\Facades\Event;
use MohamedSaad\PaymentGateways\Events\PaymentStatusChanged;
Event::listen(PaymentStatusChanged::class, function (PaymentStatusChanged $event) {
$event->result->gateway;
$event->result->transactionId;
$event->result->status;
$event->result->signatureValid;
});
Response DTOs
Checkout returns CheckoutResponseDTO:
$checkout->gateway;
$checkout->checkoutId;
$checkout->url;
$checkout->referenceCode;
$checkout->raw;
Payment status returns PaymentStatusDTO:
$status->gateway;
$status->transactionId;
$status->status;
$status->amount;
$status->currency;
$status->raw;
Refund returns RefundResponseDTO:
$refund->gateway;
$refund->refundId;
$refund->amount;
$refund->successful;
$refund->raw;
Notes
- BNPL gateways need item, customer, address, and history data for scoring.
- Tabby usually needs capture after authorization:
Tabby::capture($paymentId, $amount). - Tamara usually needs authorise before settlement:
Tamara::authorise($orderId). - Always save the gateway checkout/payment/order IDs returned in
$checkout->raw. - Test all gateways with sandbox credentials before production.