mhaggag / laravel-payments
A comprehensive payment gateway package for Laravel with support for multiple payment providers such as Stripe, PayPal, paymob, and more.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/mhaggag/laravel-payments
Requires
- php: >=8.2
- guzzlehttp/guzzle: >=7.0
- illuminate/database: >=11.0
- illuminate/http: >=11.0
- illuminate/routing: >=11.0
- illuminate/support: >=11.0
- laravel/framework: >=11.0
- paypal/paypal-checkout-sdk: ^1.0
- stripe/stripe-php: ^19.2
Requires (Dev)
- mockery/mockery: >=1.0
- phpunit/phpunit: >=11.0
This package is auto-updated.
Last update: 2026-01-27 19:45:42 UTC
README
Laravel Payments
A comprehensive and flexible Laravel package for handling payments, subscriptions, and webhooks across multiple gateways including Stripe, PayPal, and Paymob.
Requirements
- PHP: >= 8.2
- Laravel: >= 11
Table of Contents
- 1. Installation
- 2. Publishing Package Files
- 3. Configuration
- 4. Environment Variables
- 5. Supported Gateways
- 6. Using Gateway Enums
- 7. Using the Payment Facade
- 8. Using the Payable Trait
- 9. Using the Subscribable Trait
- 10. Gateway Payload & Parameters
- 11. Web & Webhook Routes
- 12. Events & Listeners
- 13. Models & Database
- 14. Views & Translations
- 15. Debug Mode & Logging
- 16. Custom Gateways
- 17. Screenshots from default routes
- 18. Testing
- 19. License
- 20. Contact
1. Installation
Install the package via Composer:
composer require mhaggag/laravel-payments
2. Publishing Package Files
Publish all package assets:
php artisan vendor:publish --provider="MHaggag\Payments\PaymentsServiceProvider"
Or publish selectively:
# Migrations (REQUIRED) php artisan vendor:publish --tag=payments-migrations # Config (OPTIONAL) php artisan vendor:publish --tag=payments-config # Views (OPTIONAL) php artisan vendor:publish --tag=payments-views # Translations (OPTIONAL) php artisan vendor:publish --tag=payments-translations
Run migrations:
php artisan migrate
3. Configuration (config/payments.php)
All package behavior is controlled from one config file:
use MHaggag\Payments\Console\PaypalWebhookCommand; use MHaggag\Payments\Console\StripeWebhookCommand; use MHaggag\Payments\Enums\GatewayName; return [ /* |-------------------------------------------------------------------------- | Default Payment Gateway |-------------------------------------------------------------------------- */ 'default' => env('PAYMENT_DEFAULT_GATEWAY', GatewayName::STRIPE), /* |-------------------------------------------------------------------------- | Default Currency |-------------------------------------------------------------------------- */ 'default_currency' => env('PAYMENT_DEFAULT_CURRENCY', 'USD'), /* |-------------------------------------------------------------------------- | Route Configuration |-------------------------------------------------------------------------- | * On enable it, will set 3 routes | POST payments/checkout ............ payments.checkout › MHaggag\Payments\Http\Controllers\PaymentController@checkout | GET payments/success/{gateway} ... payments.success › MHaggag\Payments\Http\Controllers\PaymentController@success | GET payments/cancel/{gateway} .... payments.cancel › MHaggag\Payments\Http\Controllers\PaymentController@cancel */ 'route' => [ 'enabled' => env('PAYMENT_ROUTE_ENABLED', true), 'prefix' => env('PAYMENT_ROUTE_PREFIX', 'payments'), 'middleware' => ['web'], 'name_prefix' => env('PAYMENT_ROUTE_NAME_PREFIX', 'payments.'), ], /* |-------------------------------------------------------------------------- | Model Configuration |-------------------------------------------------------------------------- */ 'models' => [ 'payment' => \MHaggag\Payments\Models\Payment::class, 'subscription' => \MHaggag\Payments\Models\Subscription::class, ], /* |-------------------------------------------------------------------------- | Payment Gateways Configuration |-------------------------------------------------------------------------- */ 'gateways' => [ GatewayName::STRIPE => [ 'class' => \MHaggag\Payments\Gateways\StripeGateway::class, 'api_key' => env('STRIPE_API_KEY'), 'secret_key' => env('STRIPE_SECRET_KEY'), 'webhook_secret' => env('STRIPE_WEBHOOK_SECRET'), 'mode' => env('STRIPE_MODE', 'test'), 'currency' => env('STRIPE_CURRENCY', 'usd'), ], GatewayName::PAYPAL => [ 'class' => \MHaggag\Payments\Gateways\PaypalGateway::class, 'client_id' => env('PAYPAL_CLIENT_ID'), 'client_secret' => env('PAYPAL_CLIENT_SECRET'), 'webhook_id' => env('PAYPAL_WEBHOOK_ID'), 'mode' => env('PAYPAL_MODE', 'sandbox'), 'currency' => env('PAYPAL_CURRENCY', 'USD'), ], GatewayName::PAYMOB => [ 'class' => \MHaggag\Payments\Gateways\PaymobGateway::class, 'api_key' => env('PAYMOB_API_KEY'), 'secret_key' => env('PAYMOB_SECRET_KEY'), 'public_key' => env('PAYMOB_PUBLIC_KEY'), 'hmac_secret' => env('PAYMOB_HMAC_SECRET'), 'integration_id' => env('PAYMOB_INTEGRATION_ID'), 'moto_integration_id' => env('PAYMOB_MOTO_INTEGRATION_ID'), 'mode' => env('PAYMOB_MODE', 'test'), 'currency' => env('PAYMOB_CURRENCY', 'EGP'), 'region' => env('PAYMOB_REGION', 'EGY'), ], ], /* |-------------------------------------------------------------------------- | Webhook Configuration |-------------------------------------------------------------------------- | | * with built in payments.verify_signature midlleware that you can remove it to prevent the signature verification. | * On enable it, will set 3 routes | POST payments/webhook/{gateway} ........ MHaggag\Payments\Http\Controllers\WebhookController@handle */ 'webhook' => [ 'enabled' => env('PAYMENT_ROUTE_WEBHOOK_ENABLED', true), 'prefix' => '/payments/webhook', 'middleware' => ['api', 'payments.verify_signature'], 'events' => [ 'stripe' => StripeWebhookCommand::DEFAULT_EVENTS, 'paypal' => PaypalWebhookCommand::DEFAULT_EVENTS, ], ], /* |-------------------------------------------------------------------------- | Events Configuration |-------------------------------------------------------------------------- */ 'events' => [ 'payment' => [ 'received' => \MHaggag\Payments\Events\PaymentRedirectReceived::class, 'processed' => \MHaggag\Payments\Events\PaymentRedirectProcessed::class, ], 'webhook' => [ 'received' => \MHaggag\Payments\Events\PaymentWebhookReceived::class, 'processed' => \MHaggag\Payments\Events\PaymentWebhookProcessed::class, ], ], /* |-------------------------------------------------------------------------- | Logging Configuration |-------------------------------------------------------------------------- */ 'logging' => [ 'enabled' => env('PAYMENT_LOGGING_ENABLED', false), 'channel' => env('PAYMENT_LOGGING_CHANNEL', 'stack'), 'level' => env('PAYMENT_LOGGING_LEVEL', 'info'), ], ];
4. Environment Variables
Add these variables to your .env file to configure the package:
# General PAYMENT_DEFAULT_GATEWAY=stripe PAYMENT_DEFAULT_CURRENCY=USD PAYMENT_ROUTE_ENABLED=true PAYMENT_ROUTE_PREFIX=payments PAYMENT_ROUTE_NAME_PREFIX=payments. PAYMENT_ROUTE_WEBHOOK_ENABLED=true PAYMENT_LOGGING_ENABLED=true PAYMENT_LOGGING_CHANNEL=stack PAYMENT_LOGGING_LEVEL=info # Stripe STRIPE_API_KEY=pk_test_... STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_MODE=test STRIPE_CURRENCY=usd # PayPal PAYPAL_CLIENT_ID=... PAYPAL_CLIENT_SECRET=... PAYPAL_WEBHOOK_ID=... PAYPAL_MODE=sandbox PAYPAL_CURRENCY=USD # Paymob PAYMOB_API_KEY=... PAYMOB_SECRET_KEY=... PAYMOB_PUBLIC_KEY=... PAYMOB_HMAC_SECRET=... PAYMOB_INTEGRATION_ID=... PAYMOB_MOTO_INTEGRATION_ID=... PAYMOB_MODE=test PAYMOB_CURRENCY=EGP PAYMOB_REGION=EGY
5. Supported Gateways
- Stripe
- PayPal
- Paymob
- Easily extendable with custom gateways
Each gateway has:
- Its own config section
- Its own payload definition
6. Using Gateway Enums
Select the gateway for a payment process using the GatewayName enum:
use MHaggag\Payments\Enums\GatewayName; GatewayName::STRIPE; GatewayName::PAYPAL; GatewayName::PAYMOB;
Example:
use MHaggag\Payments\Facades\Payment; Payment::gateway(GatewayName::Stripe);
7. Using the Payment Facade
The Payment facade provides a fluent interface to construct and execute payment requests.
Available Methods
gateway(string $name): Set the payment gateway driver, or use the default inconfig/payments.phpwithdefaultkey.withPayload(array $payload): Pass gateway-specific parameters (including the next methods parameters and any custom parameters).
Or you can set your payload as separate methods:
amount(float $amount, ?string $currency = null): Set the amount to charge.currency(string $currency): Set the currency (e.g., 'USD').for(Model $payable): Associate the payment with a model.isSubscription(bool $isSubscription = true): Flag as subscription.items(array $items): Set items. See gateway documentation for structure.metadata(array $metadata): Set metadata.description(string $description): Set description.redirectUrls(string $successUrl, string $cancelUrl): Set success and cancel URLs. Default URLs are configured inconfig/payments.phpunder theroutekey.
You can review the Gateways Payloads & Parameters section for more details and any custom parameters you may need to pass to the gateway individually.
Execution Methods
create(): Create a payment intent/transaction.checkout(): Initiate a checkout session that will returncheckout_urlfor redirect to payment page.refund(Model $payment, ?float $amount = null): Refund a payment.handleRedirect(string $gateway, array $payload): Handle gateway redirect.verifySignature(string $gateway, Request $request): Handle gateway verification signature.handleWebhook(string $gateway, array $payload): Process incoming webhooks.
Examples
Basic Payment:
use MHaggag\Payments\Facades\Payment; use MHaggag\Payments\Enums\GatewayName; Payment::gateway(GatewayName::PAYPAL) ->amount(50) ->currency('EUR') ->description('Order #123') ->redirectUrls(route('payment.success'), route('payment.cancel')) ->isSubscription() ->items([ { 'name' => 'Product A', 'price' => 10, 'quantity' => 2, }, { 'name' => 'Product B', 'price' => 20, 'quantity' => 1, }, ]) ->metadata([ 'order_id' => 123, ]) ->checkout(); // Or can you set all with Payment::gateway(GatewayName::PAYPAL) ->withPayload([ 'amount' => 50, 'currency' => 'EUR', 'description' => 'Order #123', 'success_url' => route('payment.success'), 'cancel_url' => route('payment.cancel'), 'allow_promotion_codes' => true, 'is_subscription' => false, 'items' => [ { 'name' => 'Product A', 'amount' => 10, 'quantity' => 2, }, { 'name' => 'Product B', 'amount' => 20, 'quantity' => 1, }, ], 'metadata' => [ 'order_id' => 123, ], ]) ->checkout();
8. Using the Payable Trait
Attach payments to any Eloquent model:
use MHaggag\Payments\Traits\Payable; class User extends Model { use Payable; }
Usage:
$user->payment(GatewayName::STRIPE) ->amount(100) ->currency('USD') ->checkout();
Available Methods
payment(string $gateway): Initiate a payment process, and using all previous methods in the payment facade.
payments(): Get all payments relationship.successfulPayments(): Get successful payments relationship.pendingPayments(): Get pending payments relationship.failedPayments(): Get failed payments relationship.refundedPayments(): Get refunded payments relationship.lastPayment(): Get the latest payment model.totalPaid(): Get total amount paid.totalRefunded(): Get total amount refunded.hasPayments(): Check if model has any payments.hasSuccessfulPayment(): Check if model has successful payment.hasPendingPayment(): Check if model has pending payment.
Scopes
hasPaymentWithStatus(string $status): Filter models by payment status.hasPaymentWithGateway(string $gateway): Filter models by gateway.
9. Using the Subscribable Trait
Using it with Payable Trait when you want to support subscriptions mode.
use MHaggag\Payments\Traits\Subscribable; class User extends Model { use Subscribable; }
Usage:
$user->subscribe(GatewayName::PAYPAL) ->amount(20) ->currency('USD') ->checkout();
Available Methods
subscribe(string $gateway): Initiate a subscription process, and using all previous methods in the payment facade.
subscriptions(): Get all subscriptions relationship.activeSubscriptions(): Get active subscriptions relationship.canceledSubscriptions(): Get canceled subscriptions relationship.trialingSubscriptions(): Get trialing subscriptions relationship.latestSubscription(): Get the latest subscription model.isSubscribed(): Check if model has an active subscription.onTrial(): Check if model is currently on trial.
Scopes
hasSubscriptionWithStatus(string $status): Filter models by subscription status.hasSubscriptionWithGateway(string $gateway): Filter models by gateway.
10. Gateway Payload & Parameters
Each gateway defines specific parameters.
Stripe
Payment::gateway(GatewayName::STRIPE) // Special parameters for stripe ->payload([ 'payment_method_types' => ['card'], 'customer' => $user->name, 'customer_email' => $user->email, 'client_reference_id' => $user->id, // Optional, the default will be payment->uuid 'subscription_data' => [], // if you want to create a subscription 'allow_promotion_codes' => true, 'source' => '', // Token, card reference, etc when using create method instead of checkout 'items' => [ // items can be { 'name' => 'Product A', 'amount' => 10, 'quantity' => 2, }, // Or { 'price_id' => 'stripe_price_id', 'quantity' => 1, }, // if you do not pass any items, the default will be the payment amount, and name will be description. ], ]); /** * Note: By using isSubscription() method to enable subscription mode * You must provide at least one recurring price in `subscription` mode when using prices. */ Payment::gateway(GatewayName::STRIPE) ->withPayload([]) ->isSubscription() ->items([ ['price_id' => 'price_1St7q................', 'quantity' => 1] ])
Test Card Details Card Number: 4242424242424242 Expiration Date: 12/34 CVV: 123
PayPal
Payment::gateway(GatewayName::PAYPAL) // Special parameters for paypal ->payload([ 'brand_name' => 'Your Brand', // default will be app name 'locale' => 'en_US', // default will be 'en_US' 'source' => '', // Token, card reference, etc when using create method instead of checkout 'items' => [ // items can be { 'name' => 'Product A', 'amount' => 10, 'quantity' => 2, 'description' => 'Product A description' }, // if you do not pass any items, the default will be the payment amount, and name will be description. ], ]); /** * Note: By using isSubscription() method to enable subscription mode * You must send plan_id that you created in paypal. */ Payment::gateway(GatewayName::PAYPAL) ->withPayload([ 'plan_id' => 'paypal_plan_id', ]) ->isSubscription()
Paymob
Payment::gateway(GatewayName::PAYMOB) ->withPayload([ 'notification_url' => 'notification_url', // default will be configured in config/payments.php under the webhook key 'billing_data' => [ "email" => "mhaggag@gmail.com", // required "first_name" => "mahmoud", // required "last_name" => "haggag", // required "phone_number" => "+201121xxxxxx", // required "apartment" => "dumy", // optional "street" => "dumy", // optional "building" => "dumy", // optional "city" => "dumy", // optional "country" => "dumy", // optional "floor" => "dumy", // optional "state" => "dumy" // optional ], 'items' => [ // optional [ 'name' => 'Item name', 'amount' => 100, 'quantity' => 1, 'description' => 'Item description' ], ], 'special_reference' => $user->uuid, // Optional, the default will be payment->uuid ]); /** * Note: By using isSubscription() method to enable subscription mode * You must send plan_id that you created in next step. */ $plan = Payment::driver(GatewayName::PAYMOB)->createSubscriptionPlan([ 'name' => 'Test Plan', 'amount' => 100, 'frequency' => 7, // Values can be (7, 15, 30, 60, 90, 180, 360) 'use_transaction_amount' => true, // default is true 'reminder_days' => 2, // optional 'retrial_days' => 2, // optional 'number_of_deductions' => 2, // default is null 'webhook_url' => 'webhook_url', // default will be configured in config/payments.php under the webhook key ]); Payment::gateway(GatewayName::PAYMOB) ->withPayload([ 'plan_id' => $plan['id'], ]) ->isSubscription() /** * Subscription creation is being done by completing one 3DS transaction to save the customer's card and connect it with the subscription. */
Card Test Credentials Card Number: 2223000000000007 Expiration Date: 01/39 CVV: 100 Card Holder Name: Test Account
Wallet Test Credentials Wallet Number: 01010101010 MPin Code: 123456 OTP: 123456
11. Web & Webhook Routes
Enable or disable routes from config, and by default the webhook routes use the payments.verify_signature middleware to verify the signature or use the verifySignature method on the facade. You can control all these by route and webhook keys in config/payments.php:
'route' => [ 'enabled' => env('PAYMENT_ROUTE_ENABLED', true), 'prefix' => env('PAYMENT_ROUTE_PREFIX', 'payments'), 'middleware' => ['web'], 'name_prefix' => env('PAYMENT_ROUTE_NAME_PREFIX', 'payments.'), ] 'webhook' => [ 'enabled' => env('PAYMENT_ROUTE_WEBHOOK_ENABLED', true), 'prefix' => '/payments/webhook', 'middleware' => ['api', 'payments.verify_signature'], ]
Default web URLs (you can see the screenshots below):
- POST
/payments/checkout - GET
/payments/success/{gateway} - GET
/payments/cancel/{gateway}
Default webhook URLs:
- POST
/payments/webhook/{gateway}
Also, you can create webhook events by this commands:
php artisan stripe:webhook
# Available options (optional)
--url=https://domain.com/payments/webhook/stripe
--api-version=2022-08-01
--disabled=false
php artisan paypal:webhook
# Available options (optional)
--url=https://domain.com/payments/webhook/paypal
- This command will create a webhook by default values in
config/payments.phpunder thewebhookkey. - By this key, you can custom the webhook url and events that you want to handle.
- Default webhook url is:
https://domain.com/payments/webhook/{gateway}. - And default events that will create. Will be the package need to handle the all payment/subscription process logic to all functions.
12. Events & Listeners
You can customize your events instead of using in config/payments.php by adding them to the events key:
'events' => [ 'payment' => [ 'received' => \MHaggag\Payments\Events\PaymentRedirectReceived::class, 'processed' => \MHaggag\Payments\Events\PaymentRedirectProcessed::class, ], 'webhook' => [ 'received' => \MHaggag\Payments\Events\PaymentWebhookReceived::class, 'processed' => \MHaggag\Payments\Events\PaymentWebhookProcessed::class, ], ]
Available Events on using handleRedirect method on facade
events.payment.receiveddefault isPaymentRedirectReceivedevents.payment.processeddefault isPaymentRedirectProcessed
Available Events on using handleWebhook method on facade
events.webhook.receiveddefault isPaymentWebhookReceivedevents.webhook.processeddefault isPaymentWebhookProcessed
Listening to Events
Event::listen(PaymentRedirectProcessed::class, function ($event) { logger('Payment success:', $event->payment->toArray()); }); Event::listen(PaymentWebhookProcessed::class, function ($event) { logger('Webhook processed:', $event->payment->toArray()); });
13. Models & Database
Published migrations create tables for:
- payments
- subscriptions (if enabled)
Override models in config:
'models' => [ 'payment' => \MHaggag\Payments\Models\Payment::class, 'subscription' => \MHaggag\Payments\Models\Subscription::class, ],
14. Views & Translations
Views
Publish views:
php artisan vendor:publish --tag=payments-views
Customize checkout, success, and error pages.
Translations
Publish language files:
php artisan vendor:publish --tag=payments-translations
Add your own language:
lang/fr/success.php
15. Debug Mode & Logging
Enable debug mode:
PAYMENT_LOGGING_ENABLED=true
This logs:
- Gateway requests
- Payloads
- Responses
- Webhook data
16. Custom Gateways
You can add your own gateway by extending the MHaggag\Payments\Gateways\BaseGateway class.
- Create your gateway class:
namespace App\Payments\Gateways; use MHaggag\Payments\Gateways\BaseGateway; use Illuminate\Database\Eloquent\Model; class MyCustomGateway extends BaseGateway { public function createCheckout(array $payload): array { // Implement logic to create checkout session/transaction // Return ['checkout_url' => '...', 'session_id' => '...'] } public function handleRedirect(array $payload): Model { // Verify payment status and update the payment model } // Implement other required methods... }
- Register in
config/payments.php:
'gateways' => [ 'custom_gateway' => [ 'class' => \App\Payments\Gateways\MyCustomGateway::class, 'api_key' => env('CUSTOM_API_KEY'), ], ],
17. Screenshots from default routes
Success
| English | Arabic |
|---|---|
![]() |
![]() |
Cancel
| English | Arabic |
|---|---|
![]() |
![]() |
Error
| English | Arabic |
|---|---|
![]() |
![]() |
18. Testing
php artisan test ```bash ./vendor/bin/phpunit
Mock gateways are included for local testing.
19. License
MIT License.
Final Notes
This package is designed to be:
- Framework-native
- Fully configurable
- Easy to extend
- Safe for production use
If you need custom gateways, advanced subscriptions, or multi-tenant support, extend the base gateway class or contact me for assistance.
Happy coding 🚀
20. Contact
If you have any questions, suggestions, or need support, feel free to contact me:
- Email: mahmoudhaggag641@gmail.com
- Phone: +201121300234
- LinkedIn: Mahmoud Haggag






